diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 3308f60932a..510039e86b8 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -287,7 +287,7 @@ void DecimalFormat::setGroupingUsed(UBool enabled) { if (enabled) { // Set to a reasonable default value fProperties->groupingSize = 3; - fProperties->secondaryGroupingSize = 3; + fProperties->secondaryGroupingSize = -1; } else { fProperties->groupingSize = 0; fProperties->secondaryGroupingSize = 0; diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index 9798f932d83..62007264464 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -161,7 +161,7 @@ uint64_t DecimalQuantity::getPositionFingerprint() const { } void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status) { + int32_t maxFrac, UErrorCode& status) { // TODO: This is innefficient. Improve? // TODO: Should we convert to decNumber instead? double temp = toDouble(); @@ -173,7 +173,8 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro setToDouble(temp); // Since we reset the value to a double, we need to specify the rounding boundary // in order to get the DecimalQuantity out of approximation mode. - roundToMagnitude(-minMaxFrac, roundingMode, status); + // NOTE: In Java, we have minMaxFrac, but in C++, the two are differentiated. + roundToMagnitude(-maxFrac, roundingMode, status); } void DecimalQuantity::multiplyBy(int32_t multiplicand) { @@ -454,6 +455,7 @@ int64_t DecimalQuantity::toLong() const { for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + if (isNegative()) { result = -result; } return result; } @@ -485,15 +487,15 @@ bool DecimalQuantity::fitsInLong() const { // The largest int64 is: 9,223,372,036,854,775,807 for (int p = 0; p < precision; p++) { int8_t digit = getDigit(18 - p); - static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7 }; + static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 }; if (digit < INT64_BCD[p]) { return true; } else if (digit > INT64_BCD[p]) { return false; } } - // Exactly equal to max long. - return true; + // Exactly equal to max long plus one. + return isNegative(); } double DecimalQuantity::toDouble() const { @@ -725,8 +727,8 @@ UnicodeString DecimalQuantity::toPlainString() const { sb.append(u'-'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { + if (m == -1) { sb.append(u'.'); } sb.append(getDigit(m) + u'0'); - if (m == 0) { sb.append(u'.'); } } return sb; } @@ -1046,12 +1048,13 @@ UnicodeString DecimalQuantity::toString() const { snprintf( buffer8, sizeof(buffer8), - "", + "", (lOptPos > 999 ? 999 : lOptPos), lReqPos, rReqPos, (rOptPos < -999 ? -999 : rOptPos), (usingBytes ? "bytes" : "long"), + (isNegative() ? "-" : ""), (precision == 0 ? "0" : digits.getAlias()), "E", scale); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index a3c329e265f..a776ddc48ad 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -71,7 +71,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * @param mathContext The {@link RoundingMode} to use if rounding is necessary. */ void roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status); + int32_t maxFrac, UErrorCode& status); /** * Rounds the number to a specified magnitude (power of ten). @@ -130,7 +130,6 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. - * Assumes that the DecimalQuantity is positive. */ bool fitsInLong() const; diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp index 7ec0297c6ec..8ea130fb4a5 100644 --- a/icu4c/source/i18n/number_patternstring.cpp +++ b/icu4c/source/i18n/number_patternstring.cpp @@ -14,6 +14,7 @@ #include "number_patternstring.h" #include "unicode/utf16.h" #include "number_utils.h" +#include "number_roundingutils.h" using namespace icu; using namespace icu::number; @@ -706,11 +707,11 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP } } else if (roundingInterval != 0.0) { // Rounding Interval. - digitsStringScale = minFrac; + digitsStringScale = -roundingutils::doubleFractionLength(roundingInterval); // TODO: Check for DoS here? DecimalQuantity incrementQuantity; incrementQuantity.setToDouble(roundingInterval); - incrementQuantity.adjustMagnitude(minFrac); + incrementQuantity.adjustMagnitude(-digitsStringScale); incrementQuantity.roundToMagnitude(0, kDefaultMode, status); UnicodeString str = incrementQuantity.toPlainString(); if (str.charAt(0) == u'-') { @@ -809,7 +810,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP sb.append(AffixUtils::escape(UnicodeStringCharSequence(np))); // Copy the positive digit format into the negative. // This is optional; the pattern is the same as if '#' were appended here instead. - sb.append(sb, afterPrefixPos, beforeSuffixPos); + sb.append(sb, afterPrefixPos, beforeSuffixPos - afterPrefixPos); if (!nsp.isBogus()) { sb.append(nsp); } diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index fd4dafdf983..5f794228dbc 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -9,11 +9,16 @@ #include "unicode/numberformatter.h" #include "number_types.h" #include "number_decimalquantity.h" +#include "double-conversion.h" +#include "number_roundingutils.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; + +using double_conversion::DoubleToStringConverter; + namespace { int32_t getRoundingMagnitudeFraction(int maxFrac) { @@ -46,6 +51,26 @@ int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) } +digits_t roundingutils::doubleFractionLength(double input) { + char buffer[DoubleToStringConverter::kBase10MaximalLength + 1]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + input, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + return static_cast(length - point); +} + + Rounder Rounder::unlimited() { return Rounder(RND_NONE, {}, kDefaultMode); } @@ -225,6 +250,8 @@ IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) IncrementSettings settings; settings.fIncrement = increment; settings.fMinFrac = static_cast(minFrac); + // One of the few pre-computed quantities: + settings.fMaxFrac = roundingutils::doubleFractionLength(increment); RounderUnion union_; union_.increment = settings; return {RND_INCREMENT, union_, kDefaultMode}; @@ -335,8 +362,11 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { case RND_INCREMENT: value.roundToIncrement( - fUnion.increment.fIncrement, fRoundingMode, fUnion.increment.fMinFrac, status); - value.setFractionLength(fUnion.increment.fMinFrac, fUnion.increment.fMinFrac); + fUnion.increment.fIncrement, + fRoundingMode, + fUnion.increment.fMaxFrac, + status); + value.setFractionLength(fUnion.increment.fMinFrac, INT32_MAX); break; case RND_CURRENCY: diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h index 6868ee0b868..460235a0d62 100644 --- a/icu4c/source/i18n/number_roundingutils.h +++ b/icu4c/source/i18n/number_roundingutils.h @@ -131,6 +131,12 @@ inline bool roundsAtMidpoint(int roundingMode) { } } +/** + * Computes the number of fraction digits in a double. Used for computing maxFrac for an increment. + * Calls into the DoubleToStringConverter library to do so. + */ +digits_t doubleFractionLength(double input); + } // namespace roundingutils } // namespace impl } // namespace number diff --git a/icu4c/source/i18n/number_scientific.cpp b/icu4c/source/i18n/number_scientific.cpp index 548ce625ad8..1d8b020e88e 100644 --- a/icu4c/source/i18n/number_scientific.cpp +++ b/icu4c/source/i18n/number_scientific.cpp @@ -116,6 +116,9 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m ScientificModifier &mod = micros.helpers.scientificModifier; mod.set(exponent, this); micros.modInner = &mod; + + // We already performed rounding. Do not perform it again. + micros.rounding = Rounder::constructPassThrough(); } int32_t ScientificHandler::getMultiplier(int32_t magnitude) const { diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 4cf85a0f432..c9b68a245e5 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -70,7 +70,7 @@ double ParsedNumber::getDouble() const { return l; } - // TODO: MIN_LONG + // TODO: MIN_LONG. It is supported in quantity.toLong() if quantity had the negative flag. double d = quantity.toDouble(); if (0 != (flags & FLAG_NEGATIVE)) { d *= -1; diff --git a/icu4c/source/test/intltest/numbertest_patternstring.cpp b/icu4c/source/test/intltest/numbertest_patternstring.cpp index 67d6cce6ca1..b0b1deb3d27 100644 --- a/icu4c/source/test/intltest/numbertest_patternstring.cpp +++ b/icu4c/source/test/intltest/numbertest_patternstring.cpp @@ -50,6 +50,7 @@ void PatternStringTest::testToPatternSimple() { {u"0.##", u"0.##"}, {u"0.00", u"0.00"}, {u"0.00#", u"0.00#"}, + {u"0.05", u"0.05"}, {u"#E0", u"#E0"}, {u"0E0", u"0E0"}, {u"#00E00", u"#00E00"}, @@ -57,6 +58,7 @@ void PatternStringTest::testToPatternSimple() { {u"#;#", u"0;0"}, // ignore a negative prefix pattern of '-' since that is the default: {u"#;-#", u"0"}, + {u"pp#,000;(#)", u"pp#,000;(#,000)"}, {u"**##0", u"**##0"}, {u"*'x'##0", u"*x##0"}, {u"a''b0", u"a''b0"}, diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 1f11f8da7c1..f74a6566aa7 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -318,8 +318,8 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre 1 1 0 0 3E8 // JDK gives E0 instead of allowing for unlimited precision 0 0 0 0 2.99792458E8 K -// JDK gives .299792E9; Q gives 2.99792E8 -0 1 0 5 2.9979E8 KQ +// J gives 2.9979E8 +0 1 0 5 2.99792E8 J // JDK gives 300E6 0 3 0 0 299.792458E6 K // JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)? @@ -335,8 +335,8 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre 0 0 1 5 .29979E9 // JDK gives E0 0 0 1 0 2.99792458E8 K -// JDK and Q give .2998E9 -0 0 0 4 2.998E8 KQ +// J gives 2.998E8 +0 0 0 4 .29979E9 J // According to the spec, if maxInt>minInt and minInt>1, then set // Context: #13289 2 8 1 6 2.9979246E8 K @@ -382,12 +382,12 @@ begin format maxIntegerDigits output breaks 123 1 3 0 0 0 -// Q ignores max integer if it is less than zero and prints "123" -123 -2147483648 0 Q +// C and Q ignore max integer if it is less than zero and prints "123" +123 -2147483648 0 CQ 12345 1 5 -12345 -2147483648 0 Q +12345 -2147483648 0 CQ 5.3 1 5.3 -5.3 -2147483648 .3 Q +5.3 -2147483648 .3 CQ test patterns with zero set locale en @@ -550,12 +550,12 @@ currency currencyUsage toPattern breaks // These work in J, but it prepends an extra hash sign to the pattern. // C does not print the currency rounding information in the pattern. // K does not support this feature. -USD standard 0.00 CJK -CHF standard 0.00 CJK -CZK standard 0.00 CJK -USD cash 0.00 CJK -CHF cash 0.05 CJK -CZK cash 0 CJK +USD standard 0.00 JK +CHF standard 0.00 JK +CZK standard 0.00 JK +USD cash 0.00 JK +CHF cash 0.05 JK +CZK cash 0 JK test currency rounding set locale en @@ -637,8 +637,8 @@ begin format output breaks Inf [\u221e] -Inf (\u221e) K -// Q prints the affixes -NaN NaN KQ +// J does not print the affixes +NaN [NaN] J test nan and infinity with multiplication set locale en @@ -652,18 +652,18 @@ NaN NaN K test nan and infinity with padding set locale en_US set pattern $$$0.00$ -set formatWidth 7 +set formatWidth 8 begin format padPosition output breaks -Inf beforePrefix $$$\u221e$ K -Inf afterPrefix $$$ \u221e$ K -Inf beforeSuffix $$$\u221e $ K -Inf afterSuffix $$$\u221e$ K -// Q gets $$$NaN$ -NaN beforePrefix NaN KQ -NaN afterPrefix NaN KQ -NaN beforeSuffix NaN KQ -NaN afterSuffix NaN KQ +Inf beforePrefix $$$\u221e$ K +Inf afterPrefix $$$ \u221e$ K +Inf beforeSuffix $$$\u221e $ K +Inf afterSuffix $$$\u221e$ K +// J does not print the affixes +NaN beforePrefix $$$NaN$ J +NaN afterPrefix $$$ NaN$ J +NaN beforeSuffix $$$NaN $ J +NaN afterSuffix $$$NaN$ J test apply formerly localized patterns begin @@ -689,25 +689,25 @@ test toPattern set locale en begin pattern toPattern breaks -// All of the "S" failures in this section are because of functionally equivalent patterns +// All of the C and S failures in this section are because of functionally equivalent patterns // JDK doesn't support any patterns with padding or both negative prefix and suffix // Breaks ICU4J See ticket 11671 **0,000 **0,000 JK **##0,000 **##0,000 K **###0,000 **###0,000 K -**####0,000 **#,##0,000 KS +**####0,000 **#,##0,000 CKS ###,000. #,000. -0,000 #0,000 S +0,000 #0,000 CS .00 #.00 -000 #000 S -000,000 #,000,000 S +000 #000 CS +000,000 #,000,000 CS pp#,000 pp#,000 -00.## #00.## S +00.## #00.## CS #,#00.025 #,#00.025 // No secondary grouping in JDK #,##,###.02500 #,##,###.02500 K pp#,000;(#) pp#,000;(#,000) K -**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) KS +**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) CKS // No significant digits in JDK @@### @@### K @,@#,### @,@#,### K diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index 2e1b416db36..9bd291d9cff 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -585,11 +585,10 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return result; } - static final byte[] INT64_BCD = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7 }; + static final byte[] INT64_BCD = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 }; /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. - * Assumes that the DecimalQuantity is positive. */ public boolean fitsInLong() { if (isZero()) { @@ -615,8 +614,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return false; } } - // Exactly equal to max long. - return true; + // Exactly equal to max long plus one. + return isNegative(); } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 55e4e18bc52..67fb1e30664 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -415,12 +415,13 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra @Override public String toString() { - return String.format("", + return String.format("", (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)), lReqPos, rReqPos, (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)), (usingBytes ? "bytes" : "long"), + (isNegative() ? "-" : ""), toNumberString()); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index c0939e39aec..db6917f63c4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1869,7 +1869,7 @@ public class DecimalFormat extends NumberFormat { if (enabled) { // Set to a reasonable default value properties.setGroupingSize(3); - properties.setSecondaryGroupingSize(3); + properties.setSecondaryGroupingSize(-1); } else { properties.setGroupingSize(0); properties.setSecondaryGroupingSize(0); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java index 93afd753763..e5823804f28 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java @@ -42,12 +42,14 @@ public class PatternStringTest { { "0.##", "0.##" }, { "0.00", "0.00" }, { "0.00#", "0.00#" }, + { "0.05", "0.05" }, { "#E0", "#E0" }, { "0E0", "0E0" }, { "#00E00", "#00E00" }, { "#,##0", "#,##0" }, { "#;#", "0;0" }, { "#;-#", "0" }, // ignore a negative prefix pattern of '-' since that is the default + { "pp#,000;(#)", "pp#,000;(#,000)" }, { "**##0", "**##0" }, { "*'x'##0", "*x##0" }, { "a''b0", "a''b0" },