ICU-20365 if roundingIncrement significance smaller than maxFrac, do not use it

This commit is contained in:
Peter Edberg 2019-02-14 00:59:47 -08:00 committed by pedberg-icu
parent 7fd31078bb
commit 162aaf9aa5
8 changed files with 355 additions and 20 deletions

View file

@ -735,7 +735,7 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) {
if (fDecimalQuantity->isZero()) {
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) {
(fDecimalQuantity->getMagnitude() != INT32_MIN && std::abs(fDecimalQuantity->getMagnitude()) < 5)) {
fDecimalStr->appendInvariantChars(fDecimalQuantity->toPlainString(), status);
} else {
fDecimalStr->appendInvariantChars(fDecimalQuantity->toScientificString(), status);

View file

@ -143,7 +143,11 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
if (!properties.currencyUsage.isNull()) {
precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
} else if (roundingIncrement != 0.0) {
precision = Precision::constructIncrement(roundingIncrement, minFrac);
if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
precision = Precision::constructFraction(minFrac, maxFrac);
} else {
precision = Precision::constructIncrement(roundingIncrement, minFrac);
}
} else if (explicitMinMaxSig) {
minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig

View file

@ -644,6 +644,25 @@ PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, Pars
/// End PatternStringParser.java; begin PatternStringUtils.java ///
///////////////////////////////////////////////////////////////////
// Determine whether a given roundingIncrement should be ignored for formatting
// based on the current maxFrac value (maximum fraction digits). For example a
// roundingIncrement of 0.01 should be ignored if maxFrac is 1, but not if maxFrac
// is 2 or more. Note that roundingIncrements are rounded in significance, so
// a roundingIncrement of 0.006 is treated like 0.01 for this determination, i.e.
// it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
// 0.005 is treated like 0.001 for significance). This is the reason for the
// initial doubling below.
// roundIncr must be non-zero.
bool PatternStringUtils::ignoreRoundingIncrement(double roundIncr, int32_t maxFrac) {
if (maxFrac < 0) {
return false;
}
int32_t frac = 0;
roundIncr *= 2.0;
for (frac = 0; frac <= maxFrac && roundIncr <= 1.0; frac++, roundIncr *= 10.0);
return (frac > maxFrac);
}
UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties& properties,
UErrorCode& status) {
UnicodeString sb;
@ -694,7 +713,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP
while (digitsString.length() < maxSig) {
digitsString.append(u'#');
}
} else if (roundingInterval != 0.0) {
} else if (roundingInterval != 0.0 && !ignoreRoundingIncrement(roundingInterval,maxFrac)) {
// Rounding Interval.
digitsStringScale = -roundingutils::doubleFractionLength(roundingInterval, nullptr);
// TODO: Check for DoS here?

View file

@ -222,6 +222,28 @@ class U_I18N_API PatternParser {
class U_I18N_API PatternStringUtils {
public:
/**
* Determine whether a given roundingIncrement should be ignored for formatting
* based on the current maxFrac value (maximum fraction digits). For example a
* roundingIncrement of 0.01 should be ignored if maxFrac is 1, but not if maxFrac
* is 2 or more. Note that roundingIncrements are rounded up in significance, so
* a roundingIncrement of 0.006 is treated like 0.01 for this determination, i.e.
* it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
* 0.005 is treated like 0.001 for significance).
*
* This test is needed for both NumberPropertyMapper::oldToNew and
* PatternStringUtils::propertiesToPatternString. In Java it cannot be
* exported by NumberPropertyMapper (package provate) so it is in
* PatternStringUtils, do the same in C.
*
* @param roundIncr
* The roundingIncrement to be checked. Must be non-zero.
* @param maxFrac
* The current maximum fraction digits value.
* @return true if roundIncr should be ignored for formatting.
*/
static bool ignoreRoundingIncrement(double roundIncr, int32_t maxFrac);
/**
* Creates a pattern string from a property bag.
*

View file

@ -71,6 +71,7 @@ static void TestFormatForFields(void);
static void TestRBNFRounding(void);
static void Test12052_NullPointer(void);
static void TestParseCases(void);
static void TestSetMaxFracAndRoundIncr(void);
#define TESTCASE(x) addTest(root, &x, "tsformat/cnumtst/" #x)
@ -108,6 +109,7 @@ void addNumForTest(TestNode** root)
TESTCASE(TestFormatForFields);
TESTCASE(Test12052_NullPointer);
TESTCASE(TestParseCases);
TESTCASE(TestSetMaxFracAndRoundIncr);
}
/* test Parse int 64 */
@ -1066,7 +1068,7 @@ static const ParseCurrencyItem parseCurrencyItems[] = {
{ "fr_FR", "euros8", euros8Sym, NULL, U_PARSE_ERROR, 2, 0.0, U_PARSE_ERROR, 2, 0.0, "" },
{ "fr_FR", "dollars2", dollars2Sym, NULL, U_PARSE_ERROR, 0, 0.0, U_PARSE_ERROR, 0, 0.0, "" },
{ "fr_FR", "dollars4", dollars4Sym, NULL, U_PARSE_ERROR, 0, 0.0, U_PARSE_ERROR, 0, 0.0, "" },
{ NULL, NULL, NULL, NULL, 0, 0, 0.0, 0, 0, 0.0, NULL }
};
@ -2039,7 +2041,7 @@ static void TestNBSPInPattern(void) {
}
static void TestCloneWithRBNF(void) {
static void TestCloneWithRBNF(void) {
UChar pattern[1024];
UChar pat2[512];
UErrorCode status = U_ZERO_ERROR;
@ -2474,7 +2476,7 @@ static void TestUNumberingSystem(void) {
UEnumeration * uenum;
const char * numsys;
UErrorCode status;
for (itemPtr = numSysTestItems; itemPtr->locale != NULL; itemPtr++) {
status = U_ZERO_ERROR;
unumsys = unumsys_open(itemPtr->locale, &status);
@ -2497,7 +2499,7 @@ static void TestUNumberingSystem(void) {
log_data_err("unumsys_open for locale %s fails with status %s\n", itemPtr->locale, myErrorName(status));
}
}
status = U_ZERO_ERROR;
uenum = unumsys_openAvailableNames(&status);
if ( U_SUCCESS(status) ) {
@ -2579,8 +2581,8 @@ static void TestCurrencyIsoPluralFormat(void) {
};
int32_t i, sIndex;
for (i=0; i<UPRV_LENGTHOF(DATA); ++i) {
for (i=0; i<UPRV_LENGTHOF(DATA); ++i) {
const char* localeString = DATA[i][0];
double numberToBeFormat = atof(DATA[i][1]);
const char* currencyISOCode = DATA[i][2];
@ -2591,7 +2593,7 @@ static void TestCurrencyIsoPluralFormat(void) {
UChar ubufResult[kUBufMax];
UChar ubufExpected[kUBufMax];
int32_t ulenRes;
UNumberFormat* unumFmt = unum_open(style, NULL, 0, localeString, NULL, &status);
if (U_FAILURE(status)) {
log_data_err("FAIL: unum_open, locale %s, style %d - %s\n", localeString, (int)style, myErrorName(status));
@ -2614,7 +2616,7 @@ static void TestCurrencyIsoPluralFormat(void) {
}
unum_close(unumFmt);
}
}
}
}
typedef struct {
@ -2640,7 +2642,7 @@ static const TestContextItem tcItems[] = { /* results for 123.45 */
static void TestContext(void) {
UErrorCode status = U_ZERO_ERROR;
const TestContextItem* itemPtr;
UNumberFormat *unum = unum_open(UNUM_SPELLOUT, NULL, 0, "en", NULL, &status);
if ( U_SUCCESS(status) ) {
UDisplayContext context = unum_getContext(unum, UDISPCTX_TYPE_CAPITALIZATION, &status);
@ -2661,7 +2663,7 @@ static void TestContext(void) {
for (itemPtr = tcItems; itemPtr->locale != NULL; itemPtr++) {
UChar ubufResult[kUBufMax];
int32_t ulenRes;
status = U_ZERO_ERROR;
unum = unum_open(itemPtr->style, NULL, 0, itemPtr->locale, NULL, &status);
if (U_FAILURE(status)) {
@ -2679,7 +2681,7 @@ static void TestContext(void) {
int32_t ulenExp = u_unescape(itemPtr->expectedResult, ubufExpected, kUBufMax);
if (ulenRes != ulenExp || u_strncmp(ubufResult, ubufExpected, ulenExp) != 0) {
char bbuf[kUBufMax*2];
u_austrncpy(bbuf, ubufResult, sizeof(bbuf));
u_austrncpy(bbuf, ubufResult, sizeof(bbuf));
log_err("FAIL: unum_formatDouble, locale %s, style %d, context %d, expected %d:\"%s\", got %d:\"%s\"\n",
itemPtr->locale, (int)itemPtr->style, (int)itemPtr->context, ulenExp,
itemPtr->expectedResult, ulenRes, bbuf);
@ -2738,7 +2740,7 @@ static void TestCurrencyUsage(void) {
log_err("FAIL: currency usage attribute is not UNUM_CURRENCY_CASH\n");
}
for (j=0; j<UPRV_LENGTHOF(DATA); ++j) {
for (j=0; j<UPRV_LENGTHOF(DATA); ++j) {
UChar expect[64];
int32_t expectLen;
UChar currencyCode[4];
@ -2762,7 +2764,7 @@ static void TestCurrencyUsage(void) {
}
}
unum_close(unumFmt);
}
}
@ -2963,7 +2965,7 @@ static const LocStyleAttributeTest lsaTests[] = {
{ "en", UNUM_DECIMAL_COMPACT_SHORT, UNUM_MIN_SIGNIFICANT_DIGITS, 3, enShortMin3 },
{ "ja", UNUM_DECIMAL_COMPACT_SHORT, UNUM_MAX_SIGNIFICANT_DIGITS, 2, jaShortMax2 },
{ "sr", UNUM_DECIMAL_COMPACT_LONG, UNUM_MAX_SIGNIFICANT_DIGITS, 2, srLongMax2 },
{ NULL, (UNumberFormatStyle)0, -1, 0, NULL }
{ NULL, (UNumberFormatStyle)0, -1, 0, NULL }
};
static void TestVariousStylesAndAttributes(void) {
@ -2986,7 +2988,7 @@ static void TestVariousStylesAndAttributes(void) {
UChar uexp[kUBufSize];
UChar uget[kUBufSize];
int32_t uexplen, ugetlen;
status = U_ZERO_ERROR;
uexplen = u_unescape(veItemPtr->expected, uexp, kUBufSize);
ugetlen = unum_formatDouble(unum, veItemPtr->value, uget, kUBufSize, NULL, &status);
@ -3240,4 +3242,144 @@ static void TestParseCases(void) {
}
}
typedef struct {
const char* descrip;
const char* locale;
UNumberFormatStyle style;
int32_t minInt;
int32_t minFrac;
int32_t maxFrac;
double roundIncr;
const UChar* expPattern;
double valueToFmt;
const UChar* expFormat;
} SetMaxFracAndRoundIncrItem;
static const SetMaxFracAndRoundIncrItem maxFracAndRoundIncrItems[] = {
// descrip locale style mnI mnF mxF rdInc expPat value expFmt
{ "01 en_US DEC 1/0/3/0.0", "en_US", UNUM_DECIMAL, 1, 0, 3, 0.0, u"#,##0.###", 0.128, u"0.128" },
{ "02 en_US DEC 1/0/1/0.0", "en_US", UNUM_DECIMAL, 1, 0, 1, 0.0, u"#,##0.#", 0.128, u"0.1" },
{ "03 en_US DEC 1/0/1/0.01", "en_US", UNUM_DECIMAL, 1, 0, 1, 0.01, u"#,##0.#", 0.128, u"0.1" },
{ "04 en_US DEC 1/1/1/0.01", "en_US", UNUM_DECIMAL, 1, 1, 1, 0.01, u"#,##0.0", 0.128, u"0.1" },
{ "05 en_US DEC 1/0/1/0.1", "en_US", UNUM_DECIMAL, 1, 0, 1, 0.1, u"#,##0.1", 0.128, u"0.1" }, // use incr
{ "06 en_US DEC 1/1/1/0.1", "en_US", UNUM_DECIMAL, 1, 1, 1, 0.1, u"#,##0.1", 0.128, u"0.1" }, // use incr
{ "10 en_US DEC 1/0/1/0.02", "en_US", UNUM_DECIMAL, 1, 0, 1, 0.02, u"#,##0.#", 0.128, u"0.1" },
{ "11 en_US DEC 1/0/2/0.02", "en_US", UNUM_DECIMAL, 1, 0, 2, 0.02, u"#,##0.02", 0.128, u"0.12" }, // use incr
{ "12 en_US DEC 1/0/3/0.02", "en_US", UNUM_DECIMAL, 1, 0, 3, 0.02, u"#,##0.02#", 0.128, u"0.12" }, // use incr
{ "13 en_US DEC 1/1/1/0.02", "en_US", UNUM_DECIMAL, 1, 1, 1, 0.02, u"#,##0.0", 0.128, u"0.1" },
{ "14 en_US DEC 1/1/2/0.02", "en_US", UNUM_DECIMAL, 1, 1, 2, 0.02, u"#,##0.02", 0.128, u"0.12" }, // use incr
{ "15 en_US DEC 1/1/3/0.02", "en_US", UNUM_DECIMAL, 1, 1, 3, 0.02, u"#,##0.02#", 0.128, u"0.12" }, // use incr
{ "16 en_US DEC 1/2/2/0.02", "en_US", UNUM_DECIMAL, 1, 2, 2, 0.02, u"#,##0.02", 0.128, u"0.12" }, // use incr
{ "17 en_US DEC 1/2/3/0.02", "en_US", UNUM_DECIMAL, 1, 2, 3, 0.02, u"#,##0.02#", 0.128, u"0.12" }, // use incr
{ "18 en_US DEC 1/3/3/0.02", "en_US", UNUM_DECIMAL, 1, 3, 3, 0.02, u"#,##0.020", 0.128, u"0.120" }, // use incr
{ "20 en_US DEC 1/1/1/0.0075", "en_US", UNUM_DECIMAL, 1, 1, 1, 0.0075, u"#,##0.0", 0.019, u"0.0" },
{ "21 en_US DEC 1/1/2/0.0075", "en_US", UNUM_DECIMAL, 1, 1, 2, 0.0075, u"#,##0.0075", 0.004, u"0.0075" }, // use incr
{ "22 en_US DEC 1/1/2/0.0075", "en_US", UNUM_DECIMAL, 1, 1, 2, 0.0075, u"#,##0.0075", 0.019, u"0.0225" }, // use incr
{ "23 en_US DEC 1/1/3/0.0075", "en_US", UNUM_DECIMAL, 1, 1, 3, 0.0075, u"#,##0.0075", 0.004, u"0.0075" }, // use incr
{ "24 en_US DEC 1/1/3/0.0075", "en_US", UNUM_DECIMAL, 1, 1, 3, 0.0075, u"#,##0.0075", 0.019, u"0.0225" }, // use incr
{ "25 en_US DEC 1/2/2/0.0075", "en_US", UNUM_DECIMAL, 1, 2, 2, 0.0075, u"#,##0.0075", 0.004, u"0.0075" }, // use incr
{ "26 en_US DEC 1/2/2/0.0075", "en_US", UNUM_DECIMAL, 1, 2, 2, 0.0075, u"#,##0.0075", 0.019, u"0.0225" }, // use incr
{ "27 en_US DEC 1/2/3/0.0075", "en_US", UNUM_DECIMAL, 1, 2, 3, 0.0075, u"#,##0.0075", 0.004, u"0.0075" }, // use incr
{ "28 en_US DEC 1/2/3/0.0075", "en_US", UNUM_DECIMAL, 1, 2, 3, 0.0075, u"#,##0.0075", 0.019, u"0.0225" }, // use incr
{ "29 en_US DEC 1/3/3/0.0075", "en_US", UNUM_DECIMAL, 1, 3, 3, 0.0075, u"#,##0.0075", 0.004, u"0.0075" }, // use incr
{ "2A en_US DEC 1/3/3/0.0075", "en_US", UNUM_DECIMAL, 1, 3, 3, 0.0075, u"#,##0.0075", 0.019, u"0.0225" }, // use incr
{ NULL, NULL, UNUM_IGNORE, 0, 0, 0, 0.0, NULL, 0.0, NULL }
};
// The following is copied from C++ number_patternstring.cpp for this C test.
//
// Determine whether a given roundingIncrement should be ignored for formatting
// based on the current maxFrac value (maximum fraction digits). For example a
// roundingIncrement of 0.01 should be ignored if maxFrac is 1, but not if maxFrac
// is 2 or more. Note that roundingIncrements are rounded in significance, so
// a roundingIncrement of 0.006 is treated like 0.01 for this determination, i.e.
// it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
// 0.005 is treated like 0.001 for significance). This is the reason for the
// initial doubling below.
// roundIncr must be non-zero
static UBool ignoreRoundingIncrement(double roundIncr, int32_t maxFrac) {
if (maxFrac < 0) {
return FALSE;
}
int32_t frac = 0;
roundIncr *= 2.0;
for (frac = 0; frac <= maxFrac && roundIncr <= 1.0; frac++, roundIncr *= 10.0);
return (frac > maxFrac);
}
enum { kBBufMax = 128 };
static void TestSetMaxFracAndRoundIncr(void) {
const SetMaxFracAndRoundIncrItem* itemPtr;
for (itemPtr = maxFracAndRoundIncrItems; itemPtr->descrip != NULL; itemPtr++) {
UChar ubuf[kUBufMax];
char bbufe[kBBufMax];
char bbufg[kBBufMax];
int32_t ulen;
UErrorCode status = U_ZERO_ERROR;
UNumberFormat* unf = unum_open(itemPtr->style, NULL, 0, itemPtr->locale, NULL, &status);
if (U_FAILURE(status)) {
log_data_err("locale %s: unum_open style %d fails with %s\n", itemPtr->locale, itemPtr->style, u_errorName(status));
continue;
}
unum_setAttribute(unf, UNUM_MIN_INTEGER_DIGITS, itemPtr->minInt);
unum_setAttribute(unf, UNUM_MIN_FRACTION_DIGITS, itemPtr->minFrac);
unum_setAttribute(unf, UNUM_MAX_FRACTION_DIGITS, itemPtr->maxFrac);
unum_setDoubleAttribute(unf, UNUM_ROUNDING_INCREMENT, itemPtr->roundIncr);
UBool roundIncrUsed = (itemPtr->roundIncr != 0.0 && !ignoreRoundingIncrement(itemPtr->roundIncr, itemPtr->maxFrac));
int32_t minInt = unum_getAttribute(unf, UNUM_MIN_INTEGER_DIGITS);
if (minInt != itemPtr->minInt) {
log_err("test %s: unum_getAttribute UNUM_MIN_INTEGER_DIGITS, expected %d, got %d\n",
itemPtr->descrip, itemPtr->minInt, minInt);
}
int32_t minFrac = unum_getAttribute(unf, UNUM_MIN_FRACTION_DIGITS);
if (minFrac != itemPtr->minFrac) {
log_err("test %s: unum_getAttribute UNUM_MIN_FRACTION_DIGITS, expected %d, got %d\n",
itemPtr->descrip, itemPtr->minFrac, minFrac);
}
// If incrementRounding is used, maxFrac is set equal to minFrac
int32_t maxFrac = unum_getAttribute(unf, UNUM_MAX_FRACTION_DIGITS);
// If incrementRounding is used, maxFrac is set equal to minFrac
int32_t expMaxFrac = (roundIncrUsed)? itemPtr->minFrac: itemPtr->maxFrac;
if (maxFrac != expMaxFrac) {
log_err("test %s: unum_getAttribute UNUM_MAX_FRACTION_DIGITS, expected %d, got %d\n",
itemPtr->descrip, expMaxFrac, maxFrac);
}
double roundIncr = unum_getDoubleAttribute(unf, UNUM_ROUNDING_INCREMENT);
// If incrementRounding is not used, roundIncr is set to 0.0
double expRoundIncr = (roundIncrUsed)? itemPtr->roundIncr: 0.0;
if (roundIncr != expRoundIncr) {
log_err("test %s: unum_getDoubleAttribute UNUM_ROUNDING_INCREMENT, expected %f, got %f\n",
itemPtr->descrip, expRoundIncr, roundIncr);
}
status = U_ZERO_ERROR;
ulen = unum_toPattern(unf, FALSE, ubuf, kUBufMax, &status);
if ( U_FAILURE(status) ) {
log_err("test %s: unum_toPattern fails with %s\n", itemPtr->descrip, u_errorName(status));
} else if (u_strcmp(ubuf,itemPtr->expPattern)!=0) {
u_austrcpy(bbufe, itemPtr->expPattern);
u_austrcpy(bbufg, ubuf);
log_err("test %s: unum_toPattern expect \"%s\", get \"%s\"\n", itemPtr->descrip, bbufe, bbufg);
}
status = U_ZERO_ERROR;
ulen = unum_formatDouble(unf, itemPtr->valueToFmt, ubuf, kUBufMax, NULL, &status);
if ( U_FAILURE(status) ) {
log_err("test %s: unum_formatDouble fails with %s\n", itemPtr->descrip, u_errorName(status));
} else if (u_strcmp(ubuf,itemPtr->expFormat)!=0) {
u_austrcpy(bbufe, itemPtr->expFormat);
u_austrcpy(bbufg, ubuf);
log_err("test %s: unum_formatDouble expect \"%s\", get \"%s\"\n", itemPtr->descrip, bbufe, bbufg);
}
unum_close(unf);
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -14,6 +14,39 @@ import com.ibm.icu.text.DecimalFormatSymbols;
*/
public class PatternStringUtils {
/**
* Determine whether a given roundingIncrement should be ignored for formatting
* based on the current maxFrac value (maximum fraction digits). For example a
* roundingIncrement of 0.01 should be ignored if maxFrac is 1, but not if maxFrac
* is 2 or more. Note that roundingIncrements are rounded up in significance, so
* a roundingIncrement of 0.006 is treated like 0.01 for this determination, i.e.
* it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
* 0.005 is treated like 0.001 for significance).
*
* This test is needed for both NumberPropertyMapper.oldToNew and
* PatternStringUtils.propertiesToPatternString, but NumberPropertyMapper
* is package-private so we have it here.
*
* @param roundIncrDec
* The roundingIncrement to be checked. Must be non-null.
* @param maxFrac
* The current maximum fraction digits value.
* @return true if roundIncr should be ignored for formatting.
*/
public static boolean ignoreRoundingIncrement(BigDecimal roundIncrDec, int maxFrac) {
double roundIncr = roundIncrDec.doubleValue();
if (roundIncr == 0.0) {
return true;
}
if (maxFrac < 0) {
return false;
}
int frac = 0;
roundIncr *= 2.0; // This handles the rounding up of values above e.g. 0.005 or 0.0005
for (frac = 0; frac <= maxFrac && roundIncr <= 1.0; frac++, roundIncr *= 10.0);
return (frac > maxFrac);
}
/**
* Creates a pattern string from a property bag.
*
@ -74,7 +107,7 @@ public class PatternStringUtils {
while (digitsString.length() < maxSig) {
digitsString.append('#');
}
} else if (roundingInterval != null) {
} else if (roundingInterval != null && !ignoreRoundingIncrement(roundingInterval,maxFrac)) {
// Rounding Interval.
digitsStringScale = -roundingInterval.scale();
// TODO: Check for DoS here?

View file

@ -13,6 +13,7 @@ import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringUtils;
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
@ -179,7 +180,11 @@ final class NumberPropertyMapper {
if (explicitCurrencyUsage) {
rounding = Precision.constructCurrency(currencyUsage).withCurrency(currency);
} else if (roundingIncrement != null) {
rounding = Precision.constructIncrement(roundingIncrement);
if (PatternStringUtils.ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
rounding = Precision.constructFraction(minFrac, maxFrac);
} else {
rounding = Precision.constructIncrement(roundingIncrement);
}
} else if (explicitMinMaxSig) {
minSig = minSig < 1 ? 1
: minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig;

View file

@ -48,6 +48,7 @@ import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.data.ResourceReader;
import com.ibm.icu.impl.data.TokenIterator;
import com.ibm.icu.impl.number.PatternStringUtils;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.math.MathContext;
import com.ibm.icu.text.CompactDecimalFormat;
@ -4043,6 +4044,115 @@ public class NumberFormatTest extends TestFmwk {
}
}
@Test
public void TestSetMaxFracAndRoundIncr() {
class SetMxFrAndRndIncrItem {
String descrip;
String localeID;
int style;
int minInt;
int minFrac;
int maxFrac;
double roundIncr;
String expPattern;
double valueToFmt;
String expFormat;
// Simple constructor
public SetMxFrAndRndIncrItem(String desc, String loc, int stl, int mnI, int mnF, int mxF,
double rdIn, String ePat, double val, String eFmt) {
descrip = desc;
localeID = loc;
style = stl;
minInt = mnI;
minFrac = mnF;
maxFrac = mxF;
roundIncr = rdIn;
expPattern = ePat;
valueToFmt = val;
expFormat = eFmt ;
}
};
final SetMxFrAndRndIncrItem[] items = {
// descrip locale style mnI mnF mxF rdInc expPat value expFmt
new SetMxFrAndRndIncrItem( "01 en_US DEC 1/0/3/0.0", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 3, 0.0, "#,##0.###", 0.128, "0.128" ),
new SetMxFrAndRndIncrItem( "02 en_US DEC 1/0/1/0.0", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 1, 0.0, "#,##0.#", 0.128, "0.1" ),
new SetMxFrAndRndIncrItem( "03 en_US DEC 1/0/1/0.01", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 1, 0.01, "#,##0.#", 0.128, "0.1" ),
new SetMxFrAndRndIncrItem( "04 en_US DEC 1/1/1/0.01", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 1, 0.01, "#,##0.0", 0.128, "0.1" ),
new SetMxFrAndRndIncrItem( "05 en_US DEC 1/0/1/0.1", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 1, 0.1, "#,##0.1", 0.128, "0.1" ), // use incr
new SetMxFrAndRndIncrItem( "06 en_US DEC 1/1/1/0.1", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 1, 0.1, "#,##0.1", 0.128, "0.1" ), // use incr
new SetMxFrAndRndIncrItem( "10 en_US DEC 1/0/1/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 1, 0.02, "#,##0.#", 0.128, "0.1" ),
new SetMxFrAndRndIncrItem( "11 en_US DEC 1/0/2/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 2, 0.02, "#,##0.02", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "12 en_US DEC 1/0/3/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 0, 3, 0.02, "#,##0.02#", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "13 en_US DEC 1/1/1/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 1, 0.02, "#,##0.0", 0.128, "0.1" ),
new SetMxFrAndRndIncrItem( "14 en_US DEC 1/1/2/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 2, 0.02, "#,##0.02", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "15 en_US DEC 1/1/3/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 3, 0.02, "#,##0.02#", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "16 en_US DEC 1/2/2/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 2, 0.02, "#,##0.02", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "17 en_US DEC 1/2/3/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 3, 0.02, "#,##0.02#", 0.128, "0.12" ), // use incr
new SetMxFrAndRndIncrItem( "18 en_US DEC 1/3/3/0.02", "en_US", NumberFormat.NUMBERSTYLE, 1, 3, 3, 0.02, "#,##0.020", 0.128, "0.12" ), // use incr; expFmt != ICU4C
new SetMxFrAndRndIncrItem( "20 en_US DEC 1/1/1/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 1, 0.0075, "#,##0.0", 0.019, "0.0" ),
new SetMxFrAndRndIncrItem( "21 en_US DEC 1/1/2/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 2, 0.0075, "#,##0.0075", 0.004, "0.0075" ), // use incr
new SetMxFrAndRndIncrItem( "22 en_US DEC 1/1/2/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 2, 0.0075, "#,##0.0075", 0.019, "0.0225" ), // use incr
new SetMxFrAndRndIncrItem( "23 en_US DEC 1/1/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 3, 0.0075, "#,##0.0075", 0.004, "0.0075" ), // use incr
new SetMxFrAndRndIncrItem( "24 en_US DEC 1/1/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 1, 3, 0.0075, "#,##0.0075", 0.019, "0.0225" ), // use incr
new SetMxFrAndRndIncrItem( "25 en_US DEC 1/2/2/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 2, 0.0075, "#,##0.0075", 0.004, "0.0075" ), // use incr
new SetMxFrAndRndIncrItem( "26 en_US DEC 1/2/2/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 2, 0.0075, "#,##0.0075", 0.019, "0.0225" ), // use incr
new SetMxFrAndRndIncrItem( "27 en_US DEC 1/2/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 3, 0.0075, "#,##0.0075", 0.004, "0.0075" ), // use incr
new SetMxFrAndRndIncrItem( "28 en_US DEC 1/2/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 2, 3, 0.0075, "#,##0.0075", 0.019, "0.0225" ), // use incr
new SetMxFrAndRndIncrItem( "29 en_US DEC 1/3/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 3, 3, 0.0075, "#,##0.0075", 0.004, "0.0075" ), // use incr
new SetMxFrAndRndIncrItem( "2A en_US DEC 1/3/3/0.0075", "en_US", NumberFormat.NUMBERSTYLE, 1, 3, 3, 0.0075, "#,##0.0075", 0.019, "0.0225" ), // use incr
};
for (SetMxFrAndRndIncrItem item: items) {
ULocale locale = new ULocale(item.localeID);
DecimalFormat df = (DecimalFormat)NumberFormat.getInstance(locale, item.style);
df.setMinimumIntegerDigits(item.minInt);
df.setMinimumFractionDigits(item.minFrac);
df.setMaximumFractionDigits(item.maxFrac);
df.setRoundingIncrement(item.roundIncr);
boolean roundIncrUsed = (item.roundIncr != 0 &&
!PatternStringUtils.ignoreRoundingIncrement(java.math.BigDecimal.valueOf(item.roundIncr),item.maxFrac));
int fracForRoundIncr = 0;
if (roundIncrUsed) {
double testIncr = item.roundIncr;
for (; testIncr > (double)((int)testIncr); testIncr *= 10.0, fracForRoundIncr++);
}
int minInt = df.getMinimumIntegerDigits();
if (minInt != item.minInt) {
errln("test " + item.descrip + ": getMinimumIntegerDigits, expected " + item.minInt + ", got " + minInt);
}
int minFrac = df.getMinimumFractionDigits();
int expMinFrac = (roundIncrUsed)? fracForRoundIncr: item.minFrac;
if (minFrac != expMinFrac) {
errln("test " + item.descrip + ": getMinimumFractionDigits, expected " + expMinFrac + ", got " + minFrac);
}
int maxFrac = df.getMaximumFractionDigits();
int expMaxFrac = (roundIncrUsed)? fracForRoundIncr: item.maxFrac;
if (maxFrac != expMaxFrac) {
errln("test " + item.descrip + ": getMaximumFractionDigits, expected " + expMaxFrac + ", got " + maxFrac);
}
java.math.BigDecimal bigdec = df.getRoundingIncrement(); // why doesn't this return com.ibm.icu.math.BigDecimal?
double roundIncr = (bigdec != null)? bigdec.doubleValue(): 0.0;
double expRoundIncr = (roundIncrUsed)? item.roundIncr: 0.0;
if (roundIncr != expRoundIncr) {
errln("test " + item.descrip + ": getRoundingIncrement, expected " + expRoundIncr + ", got " + roundIncr);
}
String getPattern = df.toPattern();
if (!getPattern.equals(item.expPattern)) {
errln("test " + item.descrip + ": toPattern, expected " + item.expPattern + ", got " + getPattern);
}
String getFormat = df.format(item.valueToFmt);
if (!getFormat.equals(item.expFormat)) {
errln("test " + item.descrip + ": format, expected " + item.expFormat + ", got " + getFormat);
}
}
}
@Test
public void TestBug9936() {
DecimalFormat numberFormat =