ICU-20708 Fixing edge cases with negative infinity and NaN.

This commit is contained in:
Shane Carr 2019-07-23 04:41:43 +00:00 committed by Shane F. Carr
parent 69fb255169
commit 1ef18dc761
33 changed files with 274 additions and 101 deletions

View file

@ -736,7 +736,7 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) {
fDecimalStr->append("Infinity", status);
} else if (fDecimalQuantity->isNaN()) {
fDecimalStr->append("NaN", status);
} else if (fDecimalQuantity->isZero()) {
} else if (fDecimalQuantity->isZeroish()) {
fDecimalStr->append("0", -1, status);
} else if (fType==kLong || fType==kInt64 || // use toPlainString for integer types
(fDecimalQuantity->getMagnitude() != INT32_MIN && std::abs(fDecimalQuantity->getMagnitude()) < 5)) {

View file

@ -272,15 +272,15 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
parent->processQuantity(quantity, micros, status);
if (U_FAILURE(status)) { return; }
// Treat zero as if it had magnitude 0
// Treat zero, NaN, and infinity as if they had magnitude 0
int32_t magnitude;
if (quantity.isZero()) {
if (quantity.isZeroish()) {
magnitude = 0;
micros.rounder.apply(quantity, status);
} else {
// TODO: Revisit chooseMultiplierAndApply
int32_t multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status);
magnitude = quantity.isZero() ? 0 : quantity.getMagnitude();
magnitude = quantity.isZeroish() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}

View file

@ -205,7 +205,7 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro
}
void DecimalQuantity::multiplyBy(const DecNum& multiplicand, UErrorCode& status) {
if (isInfinite() || isZero() || isNaN()) {
if (isZeroish()) {
return;
}
// Convert to DecNum, multiply, and convert back.
@ -218,7 +218,7 @@ void DecimalQuantity::multiplyBy(const DecNum& multiplicand, UErrorCode& status)
}
void DecimalQuantity::divideBy(const DecNum& divisor, UErrorCode& status) {
if (isInfinite() || isZero() || isNaN()) {
if (isZeroish()) {
return;
}
// Convert to DecNum, multiply, and convert back.
@ -318,8 +318,14 @@ bool DecimalQuantity::isNegative() const {
return (flags & NEGATIVE_FLAG) != 0;
}
int8_t DecimalQuantity::signum() const {
return isNegative() ? -1 : isZero() ? 0 : 1;
Signum DecimalQuantity::signum() const {
if (isNegative()) {
return SIGNUM_NEG;
} else if (isZeroish() && !isInfinite()) {
return SIGNUM_ZERO;
} else {
return SIGNUM_POS;
}
}
bool DecimalQuantity::isInfinite() const {
@ -330,7 +336,7 @@ bool DecimalQuantity::isNaN() const {
return (flags & NAN_FLAG) != 0;
}
bool DecimalQuantity::isZero() const {
bool DecimalQuantity::isZeroish() const {
return precision == 0;
}
@ -548,7 +554,10 @@ uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const {
}
bool DecimalQuantity::fitsInLong(bool ignoreFraction) const {
if (isZero()) {
if (isInfinite() || isNaN()) {
return false;
}
if (isZeroish()) {
return true;
}
if (scale < 0 && !ignoreFraction) {

View file

@ -146,14 +146,17 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
*/
int32_t getMagnitude() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
bool isZero() const;
/**
* @return Whether the value represented by this {@link DecimalQuantity} is
* zero, infinity, or NaN.
*/
bool isZeroish() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
bool isNegative() const;
/** @return -1 if the value is negative; 1 if positive; or 0 if zero. */
int8_t signum() const;
/** @return The appropriate value from the Signum enum. */
Signum signum() const;
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
bool isInfinite() const U_OVERRIDE;

View file

@ -697,7 +697,7 @@ void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, U
void LocalizedNumberFormatter::getAffixImpl(bool isPrefix, bool isNegative, UnicodeString& result,
UErrorCode& status) const {
FormattedStringBuilder string;
auto signum = static_cast<int8_t>(isNegative ? -1 : 1);
auto signum = static_cast<Signum>(isNegative ? SIGNUM_NEG : SIGNUM_POS);
// Always return affixes for plural form OTHER.
static const StandardPlural::Form plural = StandardPlural::OTHER;
int32_t prefixLength;

View file

@ -81,7 +81,7 @@ int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuant
return length;
}
int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
StandardPlural::Form plural,
FormattedStringBuilder& outString, UErrorCode& status) {
NumberFormatterImpl impl(macros, false, status);
@ -129,7 +129,7 @@ MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErr
return fMicros;
}
int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form plural,
int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural,
FormattedStringBuilder& outString, UErrorCode& status) const {
if (U_FAILURE(status)) { return 0; }
// #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
@ -140,7 +140,7 @@ int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form
return modifier->getPrefixLength();
}
int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
FormattedStringBuilder& outString, UErrorCode& status) {
if (U_FAILURE(status)) { return 0; }
// #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).

View file

@ -44,7 +44,7 @@ class NumberFormatterImpl : public UMemory {
* @return The index into the output at which the prefix ends and the suffix starts; in other words,
* the prefix length.
*/
static int32_t getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
static int32_t getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
StandardPlural::Form plural, FormattedStringBuilder& outString,
UErrorCode& status);
@ -61,7 +61,7 @@ class NumberFormatterImpl : public UMemory {
/**
* Like getPrefixSuffixStatic() but uses the safe compiled object.
*/
int32_t getPrefixSuffix(int8_t signum, StandardPlural::Form plural, FormattedStringBuilder& outString,
int32_t getPrefixSuffix(Signum signum, StandardPlural::Form plural, FormattedStringBuilder& outString,
UErrorCode& status) const;
const MicroProps& getRawMicroProps() const {
@ -109,7 +109,7 @@ class NumberFormatterImpl : public UMemory {
MicroProps& preProcessUnsafe(DecimalQuantity &inValue, UErrorCode &status);
int32_t getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
int32_t getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
FormattedStringBuilder& outString, UErrorCode& status);
/**

View file

@ -289,7 +289,7 @@ void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormat
if (U_FAILURE(status)) { return; }
SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, 0, plural});
fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, SIGNUM_ZERO, plural});
}
}
@ -306,7 +306,7 @@ void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFor
if (U_FAILURE(status)) { return; }
SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, 0, plural});
fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, SIGNUM_ZERO, plural});
}
}
@ -317,7 +317,7 @@ void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &mic
micros.modOuter = &fModifiers[pluralForm];
}
const Modifier* LongNameHandler::getModifier(int8_t /*signum*/, StandardPlural::Form plural) const {
const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::Form plural) const {
return &fModifiers[plural];
}

View file

@ -34,7 +34,7 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const U_OVERRIDE;
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE;
const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE;
private:
SimpleModifier fModifiers[StandardPlural::Form::COUNT];

View file

@ -57,7 +57,7 @@ Modifier::Parameters::Parameters()
: obj(nullptr) {}
Modifier::Parameters::Parameters(
const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural)
const ModifierStore* _obj, Signum _signum, StandardPlural::Form _plural)
: obj(_obj), signum(_signum), plural(_plural) {}
ModifierStore::~ModifierStore() = default;

View file

@ -289,7 +289,7 @@ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
/**
* Sets the Modifier with the specified signum and plural form.
*/
void adoptModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) {
void adoptModifier(Signum signum, StandardPlural::Form plural, const Modifier *mod) {
U_ASSERT(mods[getModIndex(signum, plural)] == nullptr);
mods[getModIndex(signum, plural)] = mod;
}
@ -298,13 +298,13 @@ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
* Sets the Modifier with the specified signum.
* The modifier will apply to all plural forms.
*/
void adoptModifierWithoutPlural(int8_t signum, const Modifier *mod) {
void adoptModifierWithoutPlural(Signum signum, const Modifier *mod) {
U_ASSERT(mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] == nullptr);
mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] = mod;
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE {
const Modifier *getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE {
const Modifier* modifier = mods[getModIndex(signum, plural)];
if (modifier == nullptr && plural != DEFAULT_STANDARD_PLURAL) {
modifier = mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
@ -313,7 +313,7 @@ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifierWithoutPlural(int8_t signum) const {
const Modifier *getModifierWithoutPlural(Signum signum) const {
return mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
}
@ -321,7 +321,7 @@ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
// NOTE: mods is zero-initialized (to nullptr)
const Modifier *mods[3 * StandardPlural::COUNT] = {};
inline static int32_t getModIndex(int8_t signum, StandardPlural::Form plural) {
inline static int32_t getModIndex(Signum signum, StandardPlural::Form plural) {
U_ASSERT(signum >= -1 && signum <= 1);
U_ASSERT(plural >= 0 && plural < StandardPlural::COUNT);
return static_cast<int32_t>(plural) * 3 + (signum + 1);

View file

@ -43,7 +43,7 @@ void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols,
fRules = rules;
}
void MutablePatternModifier::setNumberProperties(int8_t signum, StandardPlural::Form plural) {
void MutablePatternModifier::setNumberProperties(Signum signum, StandardPlural::Form plural) {
fSignum = signum;
fPlural = plural;
}
@ -79,12 +79,12 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
if (needsPlurals()) {
// Slower path when we require the plural keyword.
for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
setNumberProperties(1, plural);
pm->adoptModifier(1, plural, createConstantModifier(status));
setNumberProperties(0, plural);
pm->adoptModifier(0, plural, createConstantModifier(status));
setNumberProperties(-1, plural);
pm->adoptModifier(-1, plural, createConstantModifier(status));
setNumberProperties(SIGNUM_POS, plural);
pm->adoptModifier(SIGNUM_POS, plural, createConstantModifier(status));
setNumberProperties(SIGNUM_ZERO, plural);
pm->adoptModifier(SIGNUM_ZERO, plural, createConstantModifier(status));
setNumberProperties(SIGNUM_NEG, plural);
pm->adoptModifier(SIGNUM_NEG, plural, createConstantModifier(status));
}
if (U_FAILURE(status)) {
delete pm;
@ -93,12 +93,12 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
return new ImmutablePatternModifier(pm, fRules, parent); // adopts pm
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(1, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(1, createConstantModifier(status));
setNumberProperties(0, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(0, createConstantModifier(status));
setNumberProperties(-1, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(-1, createConstantModifier(status));
setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(SIGNUM_POS, createConstantModifier(status));
setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(SIGNUM_ZERO, createConstantModifier(status));
setNumberProperties(SIGNUM_NEG, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(SIGNUM_NEG, createConstantModifier(status));
if (U_FAILURE(status)) {
delete pm;
return nullptr;
@ -140,7 +140,7 @@ void ImmutablePatternModifier::applyToMicros(
}
}
const Modifier* ImmutablePatternModifier::getModifier(int8_t signum, StandardPlural::Form plural) const {
const Modifier* ImmutablePatternModifier::getModifier(Signum signum, StandardPlural::Form plural) const {
if (rules == nullptr) {
return pm->getModifierWithoutPlural(signum);
} else {

View file

@ -48,7 +48,7 @@ class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public U
void applyToMicros(MicroProps& micros, const DecimalQuantity& quantity, UErrorCode& status) const;
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const;
const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const;
private:
ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
@ -142,7 +142,7 @@ class U_I18N_API MutablePatternModifier
* The plural form of the number, required only if the pattern contains the triple
* currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
*/
void setNumberProperties(int8_t signum, StandardPlural::Form plural);
void setNumberProperties(Signum signum, StandardPlural::Form plural);
/**
* Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order to localize.
@ -223,7 +223,7 @@ class U_I18N_API MutablePatternModifier
const PluralRules *fRules;
// Number details (initialized in setNumberProperties)
int8_t fSignum;
Signum fSignum;
StandardPlural::Form fPlural;
// QuantityChain details (initialized in addToChain)

View file

@ -352,7 +352,7 @@ void ParsedPatternInfo::consumeIntegerFormat(UErrorCode& status) {
result.groupingSizes += 1;
result.integerNumerals += 1;
result.integerTotal += 1;
if (!result.rounding.isZero() || state.peek() != u'0') {
if (!result.rounding.isZeroish() || state.peek() != u'0') {
result.rounding.appendDigit(static_cast<int8_t>(state.peek() - u'0'), 0, true);
}
break;
@ -532,7 +532,7 @@ PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, Pars
properties.roundingIncrement = 0.0;
properties.minimumSignificantDigits = positive.integerAtSigns;
properties.maximumSignificantDigits = positive.integerAtSigns + positive.integerTrailingHashSigns;
} else if (!positive.rounding.isZero()) {
} else if (!positive.rounding.isZeroish()) {
if (!ignoreRounding) {
properties.minimumFractionDigits = minFrac;
properties.maximumFractionDigits = positive.fractionTotal;
@ -1000,7 +1000,7 @@ PatternStringUtils::convertLocalized(const UnicodeString& input, const DecimalFo
}
void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix,
int8_t signum, UNumberSignDisplay signDisplay,
Signum signum, UNumberSignDisplay signDisplay,
StandardPlural::Form plural,
bool perMilleReplacesPercent, UnicodeString& output) {
@ -1014,6 +1014,7 @@ void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider&
// Should we use the affix from the negative subpattern? (If not, we will use the positive
// subpattern.)
// TODO: Deal with signum
bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern() && (
signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));

View file

@ -295,7 +295,7 @@ class U_I18N_API PatternStringUtils {
* substitution, and plural forms for CurrencyPluralInfo.
*/
static void patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix,
int8_t signum, UNumberSignDisplay signDisplay,
Signum signum, UNumberSignDisplay signDisplay,
StandardPlural::Form plural, bool perMilleReplacesPercent,
UnicodeString& output);

View file

@ -33,7 +33,7 @@ int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig
if (maxSig == -1) {
return INT32_MIN;
}
int magnitude = value.isZero() ? 0 : value.getMagnitude();
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - maxSig + 1;
}
@ -45,7 +45,7 @@ int32_t getDisplayMagnitudeFraction(int minFrac) {
}
int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
int magnitude = value.isZero() ? 0 : value.getMagnitude();
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - minSig + 1;
}
@ -306,8 +306,8 @@ bool RoundingImpl::isSignificantDigits() const {
int32_t
RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
UErrorCode &status) {
// Do not call this method with zero.
U_ASSERT(!input.isZero());
// Do not call this method with zero, NaN, or infinity.
U_ASSERT(!input.isZeroish());
// Perform the first attempt at rounding.
int magnitude = input.getMagnitude();
@ -316,7 +316,7 @@ RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl:
apply(input, status);
// If the number rounded to zero, exit.
if (input.isZero() || U_FAILURE(status)) {
if (input.isZeroish() || U_FAILURE(status)) {
return multiplier;
}
@ -374,7 +374,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
value.setMinFraction(
uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)));
// Make sure that digits are displayed on zero.
if (value.isZero() && fPrecision.fUnion.fracSig.fMinSig > 0) {
if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
value.setMinInteger(1);
}
break;
@ -436,7 +436,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
// This method is intended for the one specific purpose of helping print "00.000E0".
U_ASSERT(isSignificantDigits());
U_ASSERT(value.isZero());
U_ASSERT(value.isZeroish());
value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
}

View file

@ -123,9 +123,15 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
fParent->processQuantity(quantity, micros, status);
if (U_FAILURE(status)) { return; }
// Do not apply scientific notation to special doubles
if (quantity.isInfinite() || quantity.isNaN()) {
micros.modInner = &micros.helpers.emptyStrongModifier;
return;
}
// Treat zero as if it had magnitude 0
int32_t exponent;
if (quantity.isZero()) {
if (quantity.isZeroish()) {
if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) {
// Show "00.000E0" on pattern "00.000E0"
micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status);

View file

@ -91,6 +91,12 @@ enum CompactType {
TYPE_DECIMAL, TYPE_CURRENCY
};
enum Signum {
SIGNUM_NEG = -1,
SIGNUM_ZERO = 0,
SIGNUM_POS = 1
};
class U_I18N_API AffixPatternProvider {
public:
@ -194,11 +200,11 @@ class U_I18N_API Modifier {
*/
struct U_I18N_API Parameters {
const ModifierStore* obj = nullptr;
int8_t signum;
Signum signum;
StandardPlural::Form plural;
Parameters();
Parameters(const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural);
Parameters(const ModifierStore* _obj, Signum _signum, StandardPlural::Form _plural);
};
/**
@ -229,7 +235,7 @@ class U_I18N_API ModifierStore {
/**
* Returns a Modifier with the given parameters (best-effort).
*/
virtual const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const = 0;
virtual const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const = 0;
};

View file

@ -281,7 +281,9 @@ void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patt
AffixPatternMatcher* posSuffix = nullptr;
// Pre-process the affix strings to resolve LDML rules like sign display.
for (int8_t signum = 1; signum >= -1; signum--) {
for (int8_t signumInt = 1; signumInt >= -1; signumInt--) {
auto signum = static_cast<Signum>(signumInt);
// Generate Prefix
bool hasPrefix = false;
PatternStringUtils::patternInfoToStringBuilder(

View file

@ -74,7 +74,7 @@ double ParsedNumber::getDouble(UErrorCode& status) const {
status = U_INVALID_STATE_ERROR;
return 0.0;
}
if (quantity.isZero() && quantity.isNegative()) {
if (quantity.isZeroish() && quantity.isNegative()) {
return -0.0;
}
@ -107,7 +107,7 @@ void ParsedNumber::populateFormattable(Formattable& output, parse_flags_t parseF
}
}
U_ASSERT(!quantity.bogus);
if (quantity.isZero() && quantity.isNegative() && !integerOnly) {
if (quantity.isZeroish() && quantity.isNegative() && !integerOnly) {
output.setDouble(-0.0);
return;
}

View file

@ -344,7 +344,7 @@ typedef enum UNumberSignDisplay {
/**
* Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a
* sign on zero.
* sign on zero or NaN, unless the sign bit is set (-0.0 gets a sign).
*
* @draft ICU 61
*/
@ -352,8 +352,9 @@ typedef enum UNumberSignDisplay {
/**
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
* positive numbers. Do not show a sign on zero. For more information on the accounting format,
* see the ACCOUNTING sign display strategy.
* positive numbers. Do not show a sign on zero or NaN, unless the sign bit is set (-0.0 gets a
* sign). For more information on the accounting format, see the ACCOUNTING sign display
* strategy.
*
* @draft ICU 61
*/

View file

@ -68,6 +68,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
// TODO: Add this method if currency symbols override support is added.
//void symbolsOverride();
void sign();
void signCoverage();
void decimal();
void scale();
void locale();

View file

@ -86,6 +86,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
// TODO: Add this method if currency symbols override support is added.
//TESTCASE_AUTO(symbolsOverride);
TESTCASE_AUTO(sign);
TESTCASE_AUTO(signCoverage);
TESTCASE_AUTO(decimal);
TESTCASE_AUTO(scale);
TESTCASE_AUTO(locale);
@ -216,6 +217,22 @@ void NumberFormatterApiTest::notationScientific() {
Locale::getEnglish(),
-1000000,
u"-1E6");
assertFormatSingle(
u"Scientific Infinity",
u"scientific",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
-uprv_getInfinity(),
u"-∞");
assertFormatSingle(
u"Scientific NaN",
u"scientific",
NumberFormatter::with().notation(Notation::scientific()),
Locale::getEnglish(),
uprv_getNaN(),
u"NaN");
}
void NumberFormatterApiTest::notationCompact() {
@ -432,6 +449,22 @@ void NumberFormatterApiTest::notationCompact() {
1e7,
u"1000\u842C");
assertFormatSingle(
u"Compact Infinity",
u"compact-short",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
-uprv_getInfinity(),
u"-∞");
assertFormatSingle(
u"Compact NaN",
u"compact-short",
NumberFormatter::with().notation(Notation::compactShort()),
Locale::getEnglish(),
uprv_getNaN(),
u"NaN");
// NOTE: There is no API for compact custom data in C++
// and thus no "Compact Somali No Figure" test
}
@ -2069,6 +2102,39 @@ void NumberFormatterApiTest::sign() {
u"-444,444.00 US dollars");
}
void NumberFormatterApiTest::signCoverage() {
// https://unicode-org.atlassian.net/browse/ICU-20708
IcuTestErrorCode status(*this, "signCoverage");
const struct TestCase {
UNumberSignDisplay sign;
const char16_t* expectedStrings[8];
} cases[] = {
{ UNUM_SIGN_AUTO, { u"-∞", u"-1", u"-0", u"0", u"1", u"", u"NaN", u"-NaN" } },
{ UNUM_SIGN_ALWAYS, { u"-∞", u"-1", u"-0", u"+0", u"+1", u"+∞", u"+NaN", u"-NaN" } },
{ UNUM_SIGN_NEVER, { u"", u"1", u"0", u"0", u"1", u"", u"NaN", u"NaN" } },
{ UNUM_SIGN_EXCEPT_ZERO, { u"-∞", u"-1", u"-0", u"0", u"+1", u"+∞", u"NaN", u"-NaN" } },
};
double negNaN = std::copysign(uprv_getNaN(), -0.0);
const double inputs[] = {
-uprv_getInfinity(), -1, -0.0, 0, 1, uprv_getInfinity(), uprv_getNaN(), negNaN
};
for (auto& cas : cases) {
auto sign = cas.sign;
for (int32_t i = 0; i < UPRV_LENGTHOF(inputs); i++) {
auto input = inputs[i];
auto expected = cas.expectedStrings[i];
auto actual = NumberFormatter::with()
.sign(sign)
.locale(Locale::getUS())
.formatDouble(input, status)
.toString(status);
assertEquals(
DoubleToUnicodeString(input) + " " + Int64ToUnicodeString(sign),
expected, actual);
}
}
}
void NumberFormatterApiTest::decimal() {
assertFormatDescending(
u"Decimal Default",

View file

@ -35,19 +35,19 @@ void PatternModifierTest::testBasic() {
}
mod.setSymbols(&symbols, &currencySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(1, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b", u"+a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setNumberProperties(0, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"+a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false);
assertEquals("Pattern a0b", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setNumberProperties(-1, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_NEG, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"-a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_NEVER, false);
@ -60,19 +60,19 @@ void PatternModifierTest::testBasic() {
assertSuccess("Spot 4", status);
mod.setPatternInfo(&patternInfo2, UNUM_FIELD_COUNT);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
mod.setNumberProperties(1, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setNumberProperties(0, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false);
assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status));
mod.setNumberProperties(-1, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_NEG, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_NEVER, false);
@ -96,7 +96,7 @@ void PatternModifierTest::testPatternWithNoPlaceholder() {
return;
}
mod.setSymbols(&symbols, &currencySymbols, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(1, StandardPlural::Form::COUNT);
mod.setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
// Unsafe Code Path
FormattedStringBuilder nsb;

View file

@ -121,8 +121,11 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal {
*/
public int getMagnitude() throws ArithmeticException;
/** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
public boolean isZero();
/**
* @return Whether the value represented by this {@link DecimalQuantity} is
* zero, infinity, or NaN.
*/
public boolean isZeroish();
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
public boolean isNegative();

View file

@ -183,7 +183,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
@Override
public void multiplyBy(BigDecimal multiplicand) {
if (isInfinite() || isZero() || isNaN()) {
if (isZeroish()) {
return;
}
BigDecimal temp = toBigDecimal();
@ -304,7 +304,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
@Override
public int signum() {
return isNegative() ? -1 : isZero() ? 0 : 1;
return isNegative() ? -1 : (isZeroish() && !isInfinite()) ? 0 : 1;
}
@Override
@ -318,7 +318,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
@Override
public boolean isZero() {
public boolean isZeroish() {
return precision == 0;
}
@ -398,8 +398,10 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
public void setToDouble(double n) {
setBcdToZero();
flags = 0;
// Double.compare() handles +0.0 vs -0.0
if (Double.compare(n, 0.0) < 0) {
// The sign bit is the top bit in both double and long, so we can
// get the long bits for the double and compare it to zero to check
// the sign of the double.
if (Double.doubleToRawLongBits(n) < 0) {
flags |= NEGATIVE_FLAG;
n = -n;
}
@ -619,7 +621,10 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
* Returns whether or not a Long can fully represent the value stored in this DecimalQuantity.
*/
public boolean fitsInLong() {
if (isZero()) {
if (isInfinite() || isNaN()) {
return false;
}
if (isZeroish()) {
return true;
}
if (scale < 0) {

View file

@ -153,7 +153,7 @@ public class ParsedNumber {
}
}
assert quantity != null;
if (quantity.isZero() && quantity.isNegative() && !integerOnly) {
if (quantity.isZeroish() && quantity.isNegative() && !integerOnly) {
return -0.0;
}

View file

@ -122,14 +122,14 @@ public class CompactNotation extends Notation {
MicroProps micros = parent.processQuantity(quantity);
assert micros.rounder != null;
// Treat zero as if it had magnitude 0
// Treat zero, NaN, and infinity as if they had magnitude 0
int magnitude;
if (quantity.isZero()) {
if (quantity.isZeroish()) {
magnitude = 0;
micros.rounder.apply(quantity);
} else {
int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
magnitude = quantity.isZero() ? 0 : quantity.getMagnitude();
magnitude = quantity.isZeroish() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}

View file

@ -346,7 +346,7 @@ public final class NumberFormatter {
/**
* Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a
* sign on zero.
* sign on zero or NaN, unless the sign bit is set (-0.0 gets a sign).
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
@ -356,8 +356,9 @@ public final class NumberFormatter {
/**
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
* positive numbers. Do not show a sign on zero. For more information on the accounting format,
* see the ACCOUNTING sign display strategy.
* positive numbers. Do not show a sign on zero or NaN, unless the sign bit is set (-0.0 gets a
* sign). For more information on the accounting format, see the ACCOUNTING sign display
* strategy.
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.

View file

@ -522,8 +522,8 @@ public abstract class Precision implements Cloneable {
* @return The number of orders of magnitude the input was adjusted by this method.
*/
int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) {
// Do not call this method with zero.
assert !input.isZero();
// Do not call this method with zero, NaN, or infinity.
assert !input.isZeroish();
// Perform the first attempt at rounding.
int magnitude = input.getMagnitude();
@ -532,7 +532,7 @@ public abstract class Precision implements Cloneable {
apply(input);
// If the number rounded to zero, exit.
if (input.isZero()) {
if (input.isZeroish()) {
return multiplier;
}
@ -603,7 +603,7 @@ public abstract class Precision implements Cloneable {
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
// Make sure that digits are displayed on zero.
if (value.isZero() && minSig > 0) {
if (value.isZeroish() && minSig > 0) {
value.setMinInteger(1);
}
}
@ -613,7 +613,7 @@ public abstract class Precision implements Cloneable {
* compatibility mode.
*/
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZero();
assert quantity.isZeroish();
quantity.setMinFraction(minSig - minInt);
}
}
@ -744,7 +744,7 @@ public abstract class Precision implements Cloneable {
if (maxSig == -1) {
return Integer.MIN_VALUE;
}
int magnitude = value.isZero() ? 0 : value.getMagnitude();
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - maxSig + 1;
}
@ -756,7 +756,7 @@ public abstract class Precision implements Cloneable {
}
private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) {
int magnitude = value.isZero() ? 0 : value.getMagnitude();
int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
return magnitude - minSig + 1;
}
}

View file

@ -5,6 +5,7 @@ package com.ibm.icu.number;
import java.text.Format.Field;
import com.ibm.icu.impl.FormattedStringBuilder;
import com.ibm.icu.impl.number.ConstantAffixModifier;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.MicroPropsGenerator;
@ -162,9 +163,15 @@ public class ScientificNotation extends Notation implements Cloneable {
MicroProps micros = parent.processQuantity(quantity);
assert micros.rounder != null;
// Do not apply scientific notation to special doubles
if (quantity.isInfinite() || quantity.isNaN()) {
micros.modInner = ConstantAffixModifier.EMPTY;
return micros;
}
// Treat zero as if it had magnitude 0
int exponent;
if (quantity.isZero()) {
if (quantity.isZeroish()) {
if (notation.requireMinInt && micros.rounder instanceof SignificantRounderImpl) {
// Show "00.000E0" on pattern "00.000E0"
((SignificantRounderImpl) micros.rounder).apply(quantity,

View file

@ -463,7 +463,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
}
@Override
public boolean isZero() {
public boolean isZeroish() {
if (primary == -1) {
return fallback.compareTo(BigDecimal.ZERO) == 0;
} else {
@ -518,7 +518,7 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
@Override
public int signum() {
return isNegative() ? -1 : isZero() ? 0 : 1;
return isNegative() ? -1 : isZeroish() ? 0 : 1;
}
private void setNegative(boolean isNegative) {

View file

@ -177,6 +177,22 @@ public class NumberFormatterApiTest {
ULocale.ENGLISH,
-1000000,
"-1E6");
assertFormatSingle(
"Scientific Infinity",
"scientific",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
Double.NEGATIVE_INFINITY,
"-∞");
assertFormatSingle(
"Scientific NaN",
"scientific",
NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
Double.NaN,
"NaN");
}
@Test
@ -386,6 +402,22 @@ public class NumberFormatterApiTest {
1e7,
"1000\u842C");
assertFormatSingle(
"Compact Infinity",
"compact-short",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
Double.NEGATIVE_INFINITY,
"-∞");
assertFormatSingle(
"Compact NaN",
"compact-short",
NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
Double.NaN,
"NaN");
Map<String, Map<String, String>> compactCustomData = new HashMap<>();
Map<String, String> entry = new HashMap<>();
entry.put("one", "Kun");
@ -1996,6 +2028,36 @@ public class NumberFormatterApiTest {
"-444,444.00 US dollars");
}
@Test
public void signCoverage() {
// https://unicode-org.atlassian.net/browse/ICU-20708
Object[][][] cases = new Object[][][] {
{ {SignDisplay.AUTO}, { "-∞", "-1", "-0", "0", "1", "", "NaN", "-NaN" } },
{ {SignDisplay.ALWAYS}, { "-∞", "-1", "-0", "+0", "+1", "+∞", "+NaN", "-NaN" } },
{ {SignDisplay.NEVER}, { "", "1", "0", "0", "1", "", "NaN", "NaN" } },
{ {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "-0", "0", "+1", "+∞", "NaN", "-NaN" } },
};
double negNaN = Math.copySign(Double.NaN, -0.0);
double inputs[] = new double[] {
Double.NEGATIVE_INFINITY, -1, -0.0, 0, 1, Double.POSITIVE_INFINITY, Double.NaN, negNaN
};
for (Object[][] cas : cases) {
SignDisplay sign = (SignDisplay) cas[0][0];
for (int i = 0; i < inputs.length; i++) {
double input = inputs[i];
String expected = (String) cas[1][i];
String actual = NumberFormatter.with()
.sign(sign)
.locale(Locale.US)
.format(input)
.toString();
assertEquals(
input + " " + sign,
expected, actual);
}
}
}
@Test
public void decimal() {
assertFormatDescending(