ICU-21886 Make rounding priority consistent with ECMA-402

See #1989
This commit is contained in:
Shane F. Carr 2022-02-19 03:29:05 +00:00
parent 4cfe96c508
commit 9bb82e60a4
9 changed files with 296 additions and 60 deletions

View file

@ -226,7 +226,8 @@ Precision FractionPrecision::withSignificantDigits(
*this,
minSignificantDigits,
maxSignificantDigits,
priority);
priority,
false);
} else {
return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
}
@ -239,7 +240,8 @@ Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const {
*this,
1,
minSignificantDigits,
UNUM_ROUNDING_PRIORITY_RELAXED);
UNUM_ROUNDING_PRIORITY_RELAXED,
true);
} else {
return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
}
@ -251,7 +253,8 @@ Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const {
return constructFractionSignificant(*this,
1,
maxSignificantDigits,
UNUM_ROUNDING_PRIORITY_STRICT);
UNUM_ROUNDING_PRIORITY_STRICT,
true);
} else {
return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
}
@ -318,11 +321,13 @@ Precision::constructFractionSignificant(
const FractionPrecision &base,
int32_t minSig,
int32_t maxSig,
UNumberRoundingPriority priority) {
UNumberRoundingPriority priority,
bool retain) {
FractionSignificantSettings settings = base.fUnion.fracSig;
settings.fMinSig = static_cast<digits_t>(minSig);
settings.fMaxSig = static_cast<digits_t>(maxSig);
settings.fPriority = priority;
settings.fRetain = retain;
PrecisionUnion union_;
union_.fracSig = settings;
return {RND_FRACTION_SIGNIFICANT, union_};
@ -457,6 +462,23 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
break;
case Precision::RND_FRACTION_SIGNIFICANT: {
// From ECMA-402:
/*
Let sResult be ToRawPrecision(...).
Let fResult be ToRawFixed(...).
If intlObj.[[RoundingType]] is morePrecision, then
If sResult.[[RoundingMagnitude]] ≤ fResult.[[RoundingMagnitude]], then
Let result be sResult.
Else,
Let result be fResult.
Else,
Assert: intlObj.[[RoundingType]] is lessPrecision.
If sResult.[[RoundingMagnitude]] ≤ fResult.[[RoundingMagnitude]], then
Let result be fResult.
Else,
Let result be sResult.
*/
int32_t roundingMag1 = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
int32_t roundingMag2 = getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig);
int32_t roundingMag;
@ -465,11 +487,35 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
} else {
roundingMag = uprv_max(roundingMag1, roundingMag2);
}
value.roundToMagnitude(roundingMag, fRoundingMode, status);
if (!value.isZeroish()) {
int32_t upperMag = value.getMagnitude();
value.roundToMagnitude(roundingMag, fRoundingMode, status);
if (!value.isZeroish() && value.getMagnitude() != upperMag && roundingMag1 == roundingMag2) {
// roundingMag2 needs to be the magnitude after rounding
roundingMag2 += 1;
}
}
int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig);
int32_t displayMag = uprv_min(displayMag1, displayMag2);
int32_t displayMag;
if (fPrecision.fUnion.fracSig.fRetain) {
// withMinDigits + withMaxDigits
displayMag = uprv_min(displayMag1, displayMag2);
} else if (fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
if (roundingMag2 <= roundingMag1) {
displayMag = displayMag2;
} else {
displayMag = displayMag1;
}
} else {
U_ASSERT(fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_STRICT);
if (roundingMag2 <= roundingMag1) {
displayMag = displayMag1;
} else {
displayMag = displayMag2;
}
}
resolvedMinFraction = uprv_max(0, -displayMag);
break;

View file

@ -1344,8 +1344,9 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr
// @, @@, @@@
maxSig = minSig;
}
UNumberRoundingPriority priority;
auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision);
if (offset < segment.length()) {
UNumberRoundingPriority priority;
if (maxSig == -1) {
// The wildcard character is not allowed with the priority annotation
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
@ -1367,22 +1368,19 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return false;
}
macros.precision = oldPrecision.withSignificantDigits(minSig, maxSig, priority);
} else if (maxSig == -1) {
// withMinDigits
maxSig = minSig;
minSig = 1;
priority = UNUM_ROUNDING_PRIORITY_RELAXED;
macros.precision = oldPrecision.withMinDigits(minSig);
} else if (minSig == 1) {
// withMaxDigits
priority = UNUM_ROUNDING_PRIORITY_STRICT;
macros.precision = oldPrecision.withMaxDigits(maxSig);
} else {
// Digits options with both min and max sig require the priority option
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return false;
}
auto& oldPrecision = static_cast<const FractionPrecision&>(macros.precision);
macros.precision = oldPrecision.withSignificantDigits(minSig, maxSig, priority);
return true;
}
@ -1617,11 +1615,21 @@ bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UE
const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig;
blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status);
sb.append(u'/');
blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
sb.append(u'r');
if (impl.fRetain) {
if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
// withMinDigits
blueprint_helpers::generateDigitsStem(impl.fMaxSig, -1, sb, status);
} else {
// withMaxDigits
blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status);
}
} else {
sb.append(u's');
blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status);
if (impl.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
sb.append(u'r');
} else {
sb.append(u's');
}
}
} else if (macros.precision.fType == Precision::RND_INCREMENT
|| macros.precision.fType == Precision::RND_INCREMENT_ONE

View file

@ -707,6 +707,11 @@ class U_I18N_API Precision : public UMemory {
impl::digits_t fMaxSig;
/** @internal (private) */
UNumberRoundingPriority fPriority;
/**
* Whether to retain trailing zeros based on the looser strategy.
* @internal (private)
*/
bool fRetain;
} fracSig;
/** @internal (private) */
struct IncrementSettings {
@ -759,7 +764,8 @@ class U_I18N_API Precision : public UMemory {
const FractionPrecision &base,
int32_t minSig,
int32_t maxSig,
UNumberRoundingPriority priority);
UNumberRoundingPriority priority,
bool retain);
static IncrementPrecision constructIncrement(double increment, int32_t minFrac);

View file

@ -75,6 +75,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void roundingFractionFigures();
void roundingOther();
void roundingIncrementRegressionTest();
void roundingPriorityCoverageTest();
void grouping();
void padding();
void integerWidth();

View file

@ -99,6 +99,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(roundingFractionFigures);
TESTCASE_AUTO(roundingOther);
TESTCASE_AUTO(roundingIncrementRegressionTest);
TESTCASE_AUTO(roundingPriorityCoverageTest);
TESTCASE_AUTO(grouping);
TESTCASE_AUTO(padding);
TESTCASE_AUTO(integerWidth);
@ -3032,6 +3033,15 @@ void NumberFormatterApiTest::roundingFigures() {
-98.7654321,
u"-98.8");
assertFormatSingle(
u"Fixed Significant at rounding boundary",
u"@@@",
u"@@@",
NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)),
Locale::getEnglish(),
9.999,
u"10.0");
assertFormatSingle(
u"Fixed Significant Zero",
u"@@@",
@ -3192,7 +3202,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
assertFormatDescending(
u"FracSig withSignificantDigits STRICT",
u"precision-integer/@#s",
u"./@#",
u"./@#s",
NumberFormatter::with().precision(Precision::maxFraction(0)
.withSignificantDigits(1, 2, UNUM_ROUNDING_PRIORITY_STRICT)),
Locale::getEnglish(),
@ -3216,7 +3226,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
1,
u"1.00");
// Trailing zeros are always retained:
// Trailing zeros follow the strategy that was chosen:
assertFormatSingle(
u"FracSig withSignificantDigits Trailing Zeros STRICT",
u".0/@@@s",
@ -3225,7 +3235,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
.withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)),
Locale::getEnglish(),
1,
u"1.00");
u"1.0");
assertFormatSingle(
u"FracSig withSignificantDigits at rounding boundary",
@ -3235,7 +3245,7 @@ void NumberFormatterApiTest::roundingFractionFigures() {
.withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)),
Locale::getEnglish(),
9.99,
u"10.0");
u"10");
assertFormatSingle(
u"FracSig with Trailing Zero Display",
@ -3619,6 +3629,67 @@ void NumberFormatterApiTest::roundingIncrementRegressionTest() {
assertEquals("ICU-21668", u"5,000", increment);
}
void NumberFormatterApiTest::roundingPriorityCoverageTest() {
IcuTestErrorCode status(*this, "roundingPriorityCoverageTest");
struct TestCase {
double input;
const char16_t* expectedRelaxed0113;
const char16_t* expectedStrict0113;
const char16_t* expectedRelaxed1133;
const char16_t* expectedStrict1133;
} cases[] = {
{ 0.9999, u"1", u"1", u"1.00", u"1.0" },
{ 9.9999, u"10", u"10", u"10.0", u"10.0" },
{ 99.999, u"100", u"100", u"100.0", u"100" },
{ 999.99, u"1000", u"1000", u"1000.0", u"1000" },
{ 0, u"0", u"0", u"0.00", u"0.0" },
{ 9.876, u"9.88", u"9.9", u"9.88", u"9.9" },
{ 9.001, u"9", u"9", u"9.00", u"9.0" },
};
for (const auto& cas : cases) {
auto precisionRelaxed0113 = Precision::minMaxFraction(0, 1)
.withSignificantDigits(1, 3, UNUM_ROUNDING_PRIORITY_RELAXED);
auto precisionStrict0113 = Precision::minMaxFraction(0, 1)
.withSignificantDigits(1, 3, UNUM_ROUNDING_PRIORITY_STRICT);
auto precisionRelaxed1133 = Precision::minMaxFraction(1, 1)
.withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_RELAXED);
auto precisionStrict1133 = Precision::minMaxFraction(1, 1)
.withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT);
auto messageBase = DoubleToUnicodeString(cas.input);
auto check = [&](
const char16_t* name,
const UnicodeString& expected,
const Precision& precision
) {
assertEquals(
messageBase + name,
expected,
NumberFormatter::withLocale(Locale::getEnglish())
.precision(precision)
.grouping(UNUM_GROUPING_OFF)
.formatDouble(cas.input, status)
.toString(status)
);
};
check(u" Relaxed 0113", cas.expectedRelaxed0113, precisionRelaxed0113);
if (status.errIfFailureAndReset()) continue;
check(u" Strict 0113", cas.expectedStrict0113, precisionStrict0113);
if (status.errIfFailureAndReset()) continue;
check(u" Relaxed 1133", cas.expectedRelaxed1133, precisionRelaxed1133);
if (status.errIfFailureAndReset()) continue;
check(u" Strict 1133", cas.expectedStrict1133, precisionStrict1133);
if (status.errIfFailureAndReset()) continue;
}
}
void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"Western Grouping",

View file

@ -41,7 +41,7 @@ public abstract class FractionPrecision extends Precision {
maxSignificantDigits >= minSignificantDigits &&
maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
this, minSignificantDigits, maxSignificantDigits, priority);
this, minSignificantDigits, maxSignificantDigits, priority, false);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
@ -74,7 +74,7 @@ public abstract class FractionPrecision extends Precision {
public Precision withMinDigits(int minSignificantDigits) {
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
this, 1, minSignificantDigits, NumberFormatter.RoundingPriority.RELAXED);
this, 1, minSignificantDigits, NumberFormatter.RoundingPriority.RELAXED, true);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG
@ -107,7 +107,7 @@ public abstract class FractionPrecision extends Precision {
public Precision withMaxDigits(int maxSignificantDigits) {
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(
this, 1, maxSignificantDigits, NumberFormatter.RoundingPriority.STRICT);
this, 1, maxSignificantDigits, NumberFormatter.RoundingPriority.STRICT, true);
} else {
throw new IllegalArgumentException("Significant digits must be between 1 and "
+ RoundingUtils.MAX_INT_FRAC_SIG

View file

@ -1335,8 +1335,9 @@ class NumberSkeletonImpl {
// @, @@, @@@
maxSig = minSig;
}
RoundingPriority priority;
FractionPrecision oldRounder = (FractionPrecision) macros.precision;
if (offset < segment.length()) {
RoundingPriority priority;
if (maxSig == -1) {
throw new SkeletonSyntaxException(
"Invalid digits option: Wildcard character not allowed with the priority annotation", segment);
@ -1355,21 +1356,18 @@ class NumberSkeletonImpl {
throw new SkeletonSyntaxException(
"Invalid digits option for fraction rounder", segment);
}
macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
} else if (maxSig == -1) {
// withMinDigits
maxSig = minSig;
minSig = 1;
priority = RoundingPriority.RELAXED;
macros.precision = oldRounder.withMinDigits(minSig);
} else if (minSig == 1) {
// withMaxDigits
priority = RoundingPriority.STRICT;
macros.precision = oldRounder.withMaxDigits(maxSig);
} else {
throw new SkeletonSyntaxException(
"Invalid digits option: Priority annotation required", segment);
}
FractionPrecision oldRounder = (FractionPrecision) macros.precision;
macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
return true;
}
@ -1577,11 +1575,19 @@ class NumberSkeletonImpl {
Precision.FracSigRounderImpl impl = (Precision.FracSigRounderImpl) macros.precision;
BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
sb.append('/');
BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
if (impl.priority == RoundingPriority.RELAXED) {
sb.append('r');
if (impl.retain) {
if (impl.priority == RoundingPriority.RELAXED) {
BlueprintHelpers.generateDigitsStem(impl.maxSig, -1, sb);
} else {
BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb);
}
} else {
sb.append('s');
BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
if (impl.priority == RoundingPriority.RELAXED) {
sb.append('r');
} else {
sb.append('s');
}
}
} else if (macros.precision instanceof Precision.IncrementRounderImpl) {
Precision.IncrementRounderImpl impl = (Precision.IncrementRounderImpl) macros.precision;

View file

@ -399,7 +399,8 @@ public abstract class Precision {
static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3);
static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3);
static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED);
static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 1, 2, RoundingPriority.RELAXED,
false);
static final IncrementFiveRounderImpl NICKEL = new IncrementFiveRounderImpl(new BigDecimal("0.05"), 2, 2);
@ -435,16 +436,16 @@ public abstract class Precision {
}
}
static Precision constructFractionSignificant(
FractionPrecision base_, int minSig, int maxSig, RoundingPriority priority) {
static Precision constructFractionSignificant(FractionPrecision base_, int minSig, int maxSig,
RoundingPriority priority, boolean retain) {
assert base_ instanceof FractionRounderImpl;
FractionRounderImpl base = (FractionRounderImpl) base_;
Precision returnValue;
if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 &&
priority == RoundingPriority.RELAXED) {
if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 1 && maxSig == 2 && priority == RoundingPriority.RELAXED
&& !retain) {
returnValue = COMPACT_STRATEGY;
} else {
returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority);
returnValue = new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig, priority, retain);
}
return returnValue.withMode(base.mathContext);
}
@ -703,13 +704,16 @@ public abstract class Precision {
final int minSig;
final int maxSig;
final RoundingPriority priority;
final boolean retain;
public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority) {
public FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig, RoundingPriority priority,
boolean retain) {
this.minFrac = minFrac;
this.maxFrac = maxFrac;
this.minSig = minSig;
this.maxSig = maxSig;
this.priority = priority;
this.retain = retain;
}
@Override
@ -722,17 +726,41 @@ public abstract class Precision {
} else {
roundingMag = Math.max(roundingMag1, roundingMag2);
}
value.roundToMagnitude(roundingMag, mathContext);
if (!value.isZeroish()) {
int upperMag = value.getMagnitude();
value.roundToMagnitude(roundingMag, mathContext);
if (!value.isZeroish() && value.getMagnitude() != upperMag && roundingMag1 == roundingMag2) {
// roundingMag2 needs to be the magnitude after rounding
roundingMag2 += 1;
}
}
int displayMag1 = getDisplayMagnitudeFraction(minFrac);
int displayMag2 = getDisplayMagnitudeSignificant(value, minSig);
int displayMag = Math.min(displayMag1, displayMag2);
int displayMag;
if (retain) {
// withMinDigits + withMaxDigits
displayMag = Math.min(displayMag1, displayMag2);
} else if (priority == RoundingPriority.RELAXED) {
if (roundingMag2 <= roundingMag1) {
displayMag = displayMag2;
} else {
displayMag = displayMag1;
}
} else {
assert(priority == RoundingPriority.STRICT);
if (roundingMag2 <= roundingMag1) {
displayMag = displayMag1;
} else {
displayMag = displayMag2;
}
}
setResolvedMinFraction(value, Math.max(0, -displayMag));
}
@Override
FracSigRounderImpl createCopy() {
FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority);
FracSigRounderImpl copy = new FracSigRounderImpl(minFrac, maxFrac, minSig, maxSig, priority, retain);
copy.mathContext = mathContext;
return copy;
}

View file

@ -2981,7 +2981,7 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
1.2,
"1.20");
assertFormatSingle(
"Hide If Whole B",
".00/w",
@ -3013,6 +3013,15 @@ public class NumberFormatterApiTest extends TestFmwk {
-98.7654321,
"-98.8");
assertFormatSingle(
"Fixed Significant at rounding boundary",
"@@@",
"@@@",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
ULocale.ENGLISH,
9.999,
"10.0");
assertFormatSingle(
"Fixed Significant Zero",
"@@@",
@ -3188,7 +3197,7 @@ public class NumberFormatterApiTest extends TestFmwk {
assertFormatDescending(
"FracSig withSignificantDigits STRICT",
"precision-integer/@#s",
"./@#",
"./@#s",
NumberFormatter.with().precision(Precision.maxFraction(0)
.withSignificantDigits(1, 2, RoundingPriority.STRICT)),
ULocale.ENGLISH,
@ -3201,7 +3210,7 @@ public class NumberFormatterApiTest extends TestFmwk {
"0",
"0",
"0");
assertFormatSingle(
"FracSig withSignificantDigits Trailing Zeros RELAXED",
".0/@@@r",
@ -3211,8 +3220,8 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
1,
"1.00");
// Trailing zeros are always retained:
// Trailing zeros follow the strategy that was chosen:
assertFormatSingle(
"FracSig withSignificantDigits Trailing Zeros STRICT",
".0/@@@s",
@ -3221,7 +3230,7 @@ public class NumberFormatterApiTest extends TestFmwk {
.withSignificantDigits(3, 3, RoundingPriority.STRICT)),
ULocale.ENGLISH,
1,
"1.00");
"1.0");
assertFormatSingle(
"FracSig withSignificantDigits at rounding boundary",
@ -3231,7 +3240,7 @@ public class NumberFormatterApiTest extends TestFmwk {
.withSignificantDigits(3, 3, RoundingPriority.STRICT)),
ULocale.ENGLISH,
9.99,
"10.0");
"10");
assertFormatSingle(
"FracSig with Trailing Zero Display",
@ -3327,7 +3336,7 @@ public class NumberFormatterApiTest extends TestFmwk {
"50",
"50",
"0");
assertFormatDescending(
"Large nickel increment with rounding mode up (ICU-21668)",
"precision-increment/5000 rounding-mode-up",
@ -3345,7 +3354,7 @@ public class NumberFormatterApiTest extends TestFmwk {
"5,000",
"5,000",
"0");
assertFormatDescending(
"Large dime increment with rounding mode up (ICU-21668)",
"precision-increment/10000 rounding-mode-up",
@ -3363,7 +3372,7 @@ public class NumberFormatterApiTest extends TestFmwk {
"10,000",
"10,000",
"0");
assertFormatDescending(
"Large non-nickel increment with rounding mode up (ICU-21668)",
"precision-increment/15000 rounding-mode-up",
@ -3607,6 +3616,67 @@ public class NumberFormatterApiTest extends TestFmwk {
assertEquals("ICU-21668", "5,000", increment);
}
static interface RoundingPriorityCheckFn {
void check(String name, String expected, Precision precision);
}
@Test
public void roundingPriorityCoverageTest() {
String[][] cases = new String[][] {
// Input, relaxed 0113, strict 0113, relaxed 1133, strict 1133
{ "0.9999", "1", "1", "1.00", "1.0" },
{ "9.9999", "10", "10", "10.0", "10.0" },
{ "99.999", "100", "100", "100.0", "100" },
{ "999.99", "1000", "1000", "1000.0", "1000" },
{ "0", "0", "0", "0.00", "0.0" },
{ "9.876", "9.88", "9.9", "9.88", "9.9" },
{ "9.001", "9", "9", "9.00", "9.0" },
};
for (String[] cas : cases) {
final double input = Double.parseDouble(cas[0]);
String expectedRelaxed0113 = cas[1];
String expectedStrict0113 = cas[2];
String expectedRelaxed1133 = cas[3];
String expectedStrict1133 = cas[4];
Precision precisionRelaxed0113 = Precision.minMaxFraction(0, 1)
.withSignificantDigits(1, 3, RoundingPriority.RELAXED);
Precision precisionStrict0113 = Precision.minMaxFraction(0, 1)
.withSignificantDigits(1, 3, RoundingPriority.STRICT);
Precision precisionRelaxed1133 = Precision.minMaxFraction(1, 1)
.withSignificantDigits(3, 3, RoundingPriority.RELAXED);
Precision precisionStrict1133 = Precision.minMaxFraction(1, 1)
.withSignificantDigits(3, 3, RoundingPriority.STRICT);
final String messageBase = cas[0];
RoundingPriorityCheckFn checker = new RoundingPriorityCheckFn() {
@Override
public void check(String name, String expected, Precision precision) {
assertEquals(
messageBase + name,
expected,
NumberFormatter.withLocale(ULocale.ENGLISH)
.precision(precision)
.grouping(GroupingStrategy.OFF)
.format(input)
.toString()
);
}
};
checker.check(" Relaxed 0113", expectedRelaxed0113, precisionRelaxed0113);
checker.check(" Strict 0113", expectedStrict0113, precisionStrict0113);
checker.check(" Relaxed 1133", expectedRelaxed1133, precisionRelaxed1133);
checker.check(" Strict 1133", expectedStrict1133, precisionStrict1133);
}
}
@Test
public void grouping() {
assertFormatDescendingBig(
@ -4521,7 +4591,7 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
444444,
"444,444");
assertFormatSingle(
"Sign Negative Negative",
"sign-negative",
@ -4530,7 +4600,7 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
-444444,
"-444,444");
assertFormatSingle(
"Sign Negative Negative Zero",
"sign-negative",
@ -4539,7 +4609,7 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
-0.0000001,
"0");
assertFormatSingle(
"Sign Accounting-Negative Positive",
"currency/USD sign-accounting-negative",
@ -4548,7 +4618,7 @@ public class NumberFormatterApiTest extends TestFmwk {
ULocale.ENGLISH,
444444,
"$444,444.00");
assertFormatSingle(
"Sign Accounting-Negative Negative",
"currency/USD sign-accounting-negative",