ICU-13701 Refactoring DecimalQuantity: removing lOptPos/rOptPos.

Combined ICU4C and ICU4J.
This commit is contained in:
Shane Carr 2018-10-24 21:25:39 -07:00 committed by Shane F. Carr
parent 59006770ed
commit a1cc16ccd3
14 changed files with 261 additions and 218 deletions

View file

@ -112,10 +112,8 @@ DecimalQuantity& DecimalQuantity::operator=(DecimalQuantity&& src) U_NOEXCEPT {
void DecimalQuantity::copyFieldsFrom(const DecimalQuantity& other) {
bogus = other.bogus;
lOptPos = other.lOptPos;
lReqPos = other.lReqPos;
rReqPos = other.rReqPos;
rOptPos = other.rOptPos;
scale = other.scale;
precision = other.precision;
flags = other.flags;
@ -125,18 +123,15 @@ void DecimalQuantity::copyFieldsFrom(const DecimalQuantity& other) {
}
void DecimalQuantity::clear() {
lOptPos = INT32_MAX;
lReqPos = 0;
rReqPos = 0;
rOptPos = INT32_MIN;
flags = 0;
setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
}
void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) {
void DecimalQuantity::setMinInteger(int32_t minInt) {
// Validation should happen outside of DecimalQuantity, e.g., in the Precision class.
U_ASSERT(minInt >= 0);
U_ASSERT(maxInt >= minInt);
// Special behavior: do not set minInt to be less than what is already set.
// This is so significant digits rounding can set the integer length.
@ -145,28 +140,37 @@ void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) {
}
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
lOptPos = maxInt;
lReqPos = minInt;
}
void DecimalQuantity::setFractionLength(int32_t minFrac, int32_t maxFrac) {
void DecimalQuantity::setMinFraction(int32_t minFrac) {
// Validation should happen outside of DecimalQuantity, e.g., in the Precision class.
U_ASSERT(minFrac >= 0);
U_ASSERT(maxFrac >= minFrac);
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
rReqPos = -minFrac;
rOptPos = -maxFrac;
}
void DecimalQuantity::applyMaxInteger(int32_t maxInt) {
// Validation should happen outside of DecimalQuantity, e.g., in the Precision class.
U_ASSERT(maxInt >= 0);
if (precision == 0) {
return;
}
int32_t magnitude = getMagnitude();
if (maxInt <= magnitude) {
popFromLeft(magnitude - maxInt + 1);
compact();
}
}
uint64_t DecimalQuantity::getPositionFingerprint() const {
uint64_t fingerprint = 0;
fingerprint ^= lOptPos;
fingerprint ^= (lReqPos << 16);
fingerprint ^= (static_cast<uint64_t>(rReqPos) << 32);
fingerprint ^= (static_cast<uint64_t>(rOptPos) << 48);
return fingerprint;
}
@ -280,7 +284,7 @@ int32_t DecimalQuantity::getUpperDisplayMagnitude() const {
U_ASSERT(!isApproximate);
int32_t magnitude = scale + precision;
int32_t result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
int32_t result = (lReqPos > magnitude) ? lReqPos : magnitude;
return result - 1;
}
@ -290,7 +294,7 @@ int32_t DecimalQuantity::getLowerDisplayMagnitude() const {
U_ASSERT(!isApproximate);
int32_t magnitude = scale;
int32_t result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
int32_t result = (rReqPos < magnitude) ? rReqPos : magnitude;
return result;
}
@ -511,7 +515,7 @@ int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const {
// if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ }
// Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
uint64_t result = 0L;
int32_t upperMagnitude = std::min(scale + precision, lOptPos) - 1;
int32_t upperMagnitude = scale + precision - 1;
if (truncateIfOverflow) {
upperMagnitude = std::min(upperMagnitude, 17);
}
@ -527,7 +531,7 @@ int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const {
uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const {
uint64_t result = 0L;
int32_t magnitude = -1;
int32_t lowerMagnitude = std::max(scale, rOptPos);
int32_t lowerMagnitude = scale;
if (includeTrailingZeros) {
lowerMagnitude = std::min(lowerMagnitude, rReqPos);
}
@ -884,10 +888,8 @@ UnicodeString DecimalQuantity::toScientificString() const {
result.append(u"0E+0", -1);
return result;
}
// NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from
// rOptPos (aka -maxFrac) due to overflow.
int32_t upperPos = std::min(precision + scale, lOptPos) - scale - 1;
int32_t lowerPos = std::max(scale, rOptPos) - scale;
int32_t upperPos = precision - 1;
int32_t lowerPos = 0;
int32_t p = upperPos;
result.append(u'0' + getDigitPos(p));
if ((--p) >= lowerPos) {
@ -985,6 +987,18 @@ void DecimalQuantity::shiftRight(int32_t numDigits) {
precision -= numDigits;
}
void DecimalQuantity::popFromLeft(int32_t numDigits) {
if (usingBytes) {
int i = precision - 1;
for (; i >= precision - numDigits; i--) {
fBCD.bcdBytes.ptr[i] = 0;
}
} else {
fBCD.bcdLong &= (static_cast<uint64_t>(1) << ((precision - numDigits) * 4)) - 1;
}
precision -= numDigits;
}
void DecimalQuantity::setBcdToZero() {
if (usingBytes) {
uprv_free(fBCD.bcdBytes.ptr);
@ -1239,10 +1253,8 @@ bool DecimalQuantity::operator==(const DecimalQuantity& other) const {
scale == other.scale
&& precision == other.precision
&& flags == other.flags
&& lOptPos == other.lOptPos
&& lReqPos == other.lReqPos
&& rReqPos == other.rReqPos
&& rOptPos == other.rOptPos
&& isApproximate == other.isApproximate;
if (!basicEquals) {
return false;
@ -1272,11 +1284,9 @@ UnicodeString DecimalQuantity::toString() const {
snprintf(
buffer8,
sizeof(buffer8),
"<DecimalQuantity %d:%d:%d:%d %s %s%s%s%d>",
(lOptPos > 999 ? 999 : lOptPos),
"<DecimalQuantity %d:%d %s %s%s%s%d>",
lReqPos,
rReqPos,
(rOptPos < -999 ? -999 : rOptPos),
(usingBytes ? "bytes" : "long"),
(isNegative() ? "-" : ""),
(precision == 0 ? "0" : digits.getAlias()),

View file

@ -53,22 +53,28 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
DecimalQuantity &operator=(DecimalQuantity&& src) U_NOEXCEPT;
/**
* Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
* Sets the minimum integer digits that this {@link DecimalQuantity} should generate.
* This method does not perform rounding.
*
* @param minInt The minimum number of integer digits.
* @param maxInt The maximum number of integer digits.
*/
void setIntegerLength(int32_t minInt, int32_t maxInt);
void setMinInteger(int32_t minInt);
/**
* Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
* Sets the minimum fraction digits that this {@link DecimalQuantity} should generate.
* This method does not perform rounding.
*
* @param minFrac The minimum number of fraction digits.
* @param maxFrac The maximum number of fraction digits.
*/
void setFractionLength(int32_t minFrac, int32_t maxFrac);
void setMinFraction(int32_t minFrac);
/**
* Truncates digits from the upper magnitude of the number in order to satisfy the
* specified maximum number of integer digits.
*
* @param maxInt The maximum number of integer digits.
*/
void applyMaxInteger(int32_t maxInt);
/**
* Rounds the number to a specified interval, such as 0.05.
@ -336,36 +342,11 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
*/
int32_t origDelta;
// Four positions: left optional '(', left required '[', right required ']', right optional ')'.
// These four positions determine which digits are displayed in the output string. They do NOT
// affect rounding. These positions are internal-only and can be specified only by the public
// endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
//
// * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
// * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
// and are displayed unless they are trailing off the left or right edge of the number and
// have a numerical value of zero. In order to be "trailing", the digits need to be beyond
// the decimal point in their respective directions.
// * Digits outside of the "optional zone" are never displayed.
//
// See the table below for illustrative examples.
//
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
// | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
// | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
// | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
// | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
// | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
// | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
// +---------+---------+---------+---------+------------+------------------------+--------------+
//
int32_t lOptPos = INT32_MAX;
// Positions to keep track of leading and trailing zeros.
// lReqPos is the magnitude of the first required leading zero.
// rReqPos is the magnitude of the last required trailing zero.
int32_t lReqPos = 0;
int32_t rReqPos = 0;
int32_t rOptPos = INT32_MIN;
/**
* The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
@ -421,8 +402,22 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
*/
void shiftLeft(int32_t numDigits);
/**
* Directly removes digits from the end of the BCD list.
* Updates the scale and precision.
*
* CAUTION: it is the caller's responsibility to call {@link #compact} after this method.
*/
void shiftRight(int32_t numDigits);
/**
* Directly removes digits from the front of the BCD list.
* Updates precision.
*
* CAUTION: it is the caller's responsibility to call {@link #compact} after this method.
*/
void popFromLeft(int32_t numDigits);
/**
* Sets the internal representation to zero. Clears any values stored in scale, precision,
* hasDouble, origDouble, origDelta, and BCD data.

View file

@ -43,14 +43,15 @@ void IntegerWidth::apply(impl::DecimalQuantity& quantity, UErrorCode& status) co
if (fHasError) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else if (fUnion.minMaxInt.fMaxInt == -1) {
quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, INT32_MAX);
quantity.setMinInteger(fUnion.minMaxInt.fMinInt);
} else {
// Enforce the backwards-compatibility feature "FormatFailIfMoreThanMaxDigits"
if (fUnion.minMaxInt.fFormatFailIfMoreThanMaxDigits &&
fUnion.minMaxInt.fMaxInt < quantity.getMagnitude()) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, fUnion.minMaxInt.fMaxInt);
quantity.setMinInteger(fUnion.minMaxInt.fMinInt);
quantity.applyMaxInteger(fUnion.minMaxInt.fMaxInt);
}
}

View file

@ -348,9 +348,8 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
fRoundingMode,
status);
value.setFractionLength(
uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)),
INT32_MAX);
value.setMinFraction(
uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)));
break;
case Precision::RND_SIGNIFICANT:
@ -358,12 +357,11 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
fRoundingMode,
status);
value.setFractionLength(
uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)),
INT32_MAX);
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) {
value.setIntegerLength(1, INT32_MAX);
value.setMinInteger(1);
}
break;
@ -384,7 +382,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
roundingMag = uprv_min(roundingMag, candidate);
}
value.roundToMagnitude(roundingMag, fRoundingMode, status);
value.setFractionLength(uprv_max(0, -displayMag), INT32_MAX);
value.setMinFraction(uprv_max(0, -displayMag));
break;
}
@ -394,7 +392,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const
fRoundingMode,
fPrecision.fUnion.increment.fMaxFrac,
status);
value.setFractionLength(fPrecision.fUnion.increment.fMinFrac, INT32_MAX);
value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
break;
case Precision::RND_CURRENCY:
@ -408,7 +406,7 @@ void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCod
// This method is intended for the one specific purpose of helping print "00.000E0".
U_ASSERT(isSignificantDigits());
U_ASSERT(value.isZero());
value.setFractionLength(fPrecision.fUnion.fracSig.fMinSig - minInt, INT32_MAX);
value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -77,27 +77,30 @@ void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
UErrorCode status = U_ZERO_ERROR;
DecimalQuantity fq;
assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 0E0>");
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 0E0>");
fq.setToInt(51423);
assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E0>");
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E0>");
fq.adjustMagnitude(-3);
assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E-3>");
fq.setToLong(999999999999000L);
assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
fq.setIntegerLength(2, 5);
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
fq.setFractionLength(3, 6);
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E-3>");
fq.setToLong(90909090909000L);
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 90909090909E3>");
fq.setMinInteger(2);
fq.applyMaxInteger(5);
assertToStringAndHealth(fq, u"<DecimalQuantity 2:0 long 9E3>");
fq.setMinFraction(3);
assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 9E3>");
fq.setToDouble(987.654321);
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToInfinity();
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status);
assertSuccess("Rounding to increment", status);
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987655E-3>");
fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
assertSuccess("Rounding to magnitude", status);
assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 98766E-2>");
}
void DecimalQuantityTest::testSwitchStorage() {
@ -119,6 +122,15 @@ void DecimalQuantityTest::testSwitchStorage() {
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString());
assertHealth(fq);
// Bytes with popFromLeft
fq.setToDecNumber({"999999999999999999"}, status);
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 999999999999999999E0>");
fq.applyMaxInteger(17);
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 99999999999999999E0>");
fq.applyMaxInteger(16);
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 9999999999999999E0>");
fq.applyMaxInteger(15);
assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 999999999999999E0>");
}
void DecimalQuantityTest::testCopyMove() {
@ -127,21 +139,21 @@ void DecimalQuantityTest::testCopyMove() {
DecimalQuantity a;
a.setToLong(1234123412341234L);
DecimalQuantity b = a; // copy constructor
assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
assertToStringAndHealth(a, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
DecimalQuantity c(std::move(a)); // move constructor
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
c.setToLong(54321L);
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 54321E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 54321E0>");
c = b; // copy assignment
assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
b.setToLong(45678);
c.setToLong(56789);
c = std::move(b); // move assignment
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 45678E0>");
a = std::move(c); // move assignment to a defunct object
assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
assertToStringAndHealth(a, u"<DecimalQuantity 0:0 long 45678E0>");
}
// Large numbers (requires byte allocation)
@ -150,21 +162,21 @@ void DecimalQuantityTest::testCopyMove() {
DecimalQuantity a;
a.setToDecNumber({"1234567890123456789", -1}, status);
DecimalQuantity b = a; // copy constructor
assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
assertToStringAndHealth(a, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
DecimalQuantity c(std::move(a)); // move constructor
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
c.setToDecNumber({"9876543210987654321", -1}, status);
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 9876543210987654321E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 9876543210987654321E0>");
c = b; // copy assignment
assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
b.setToDecNumber({"876543210987654321", -1}, status);
c.setToDecNumber({"987654321098765432", -1}, status);
c = std::move(b); // move assignment
assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
a = std::move(c); // move assignment to a defunct object
assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
assertToStringAndHealth(a, u"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
}
}
@ -364,8 +376,10 @@ void DecimalQuantityTest::testMaxDigits() {
DecimalQuantity dq;
dq.setToDouble(876.543);
dq.roundToInfinity();
dq.setIntegerLength(0, 2);
dq.setFractionLength(0, 2);
dq.setMinInteger(0);
dq.applyMaxInteger(2);
dq.setMinFraction(0);
dq.roundToMagnitude(-2, UNUM_ROUND_FLOOR, status);
assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
assertEquals("Should trim, toLong", 76LL, dq.toLong(true));
@ -376,9 +390,7 @@ void DecimalQuantityTest::testMaxDigits() {
dq.toDecNum(dn, status);
DecimalQuantity copy;
copy.setToDecNum(dn, status);
if (!logKnownIssue("13701")) {
assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString());
}
assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString());
}
void DecimalQuantityTest::testNickelRounding() {

View file

@ -27,26 +27,31 @@ import com.ibm.icu.text.UFieldPosition;
*/
public interface DecimalQuantity extends PluralRules.IFixedDecimal {
/**
* Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
* Sets the minimum integer digits that this {@link DecimalQuantity} should generate.
* This method does not perform rounding.
*
* @param minInt
* The minimum number of integer digits.
* @param maxInt
* The maximum number of integer digits.
*/
public void setIntegerLength(int minInt, int maxInt);
public void setMinInteger(int minInt);
/**
* Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
* Sets the minimum fraction digits that this {@link DecimalQuantity} should generate.
* This method does not perform rounding.
*
* @param minFrac
* The minimum number of fraction digits.
* @param maxFrac
* The maximum number of fraction digits.
*/
public void setFractionLength(int minFrac, int maxFrac);
public void setMinFraction(int minFrac);
/**
* Truncates digits from the upper magnitude of the number in order to satisfy the
* specified maximum number of integer digits.
*
* @param maxInt
* The maximum number of integer digits.
*/
public void applyMaxInteger(int maxInt);
/**
* Rounds the number to a specified interval, such as 0.05.

View file

@ -79,45 +79,18 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
*/
protected boolean isApproximate;
// Four positions: left optional '(', left required '[', right required ']', right optional ')'.
// These four positions determine which digits are displayed in the output string. They do NOT
// affect rounding. These positions are internal-only and can be specified only by the public
// endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
//
// * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
// * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
// and are displayed unless they are trailing off the left or right edge of the number and
// have a numerical value of zero. In order to be "trailing", the digits need to be beyond
// the decimal point in their respective directions.
// * Digits outside of the "optional zone" are never displayed.
//
// See the table below for illustrative examples.
//
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
// +---------+---------+---------+---------+------------+------------------------+--------------+
// | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
// | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
// | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
// | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
// | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
// | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
// | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
// +---------+---------+---------+---------+------------+------------------------+--------------+
//
protected int lOptPos = Integer.MAX_VALUE;
// Positions to keep track of leading and trailing zeros.
// lReqPos is the magnitude of the first required leading zero.
// rReqPos is the magnitude of the last required trailing zero.
protected int lReqPos = 0;
protected int rReqPos = 0;
protected int rOptPos = Integer.MIN_VALUE;
@Override
public void copyFrom(DecimalQuantity _other) {
copyBcdFrom(_other);
DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
lOptPos = other.lOptPos;
lReqPos = other.lReqPos;
rReqPos = other.rReqPos;
rOptPos = other.rOptPos;
scale = other.scale;
precision = other.precision;
flags = other.flags;
@ -127,20 +100,17 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
public DecimalQuantity_AbstractBCD clear() {
lOptPos = Integer.MAX_VALUE;
lReqPos = 0;
rReqPos = 0;
rOptPos = Integer.MIN_VALUE;
flags = 0;
setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
return this;
}
@Override
public void setIntegerLength(int minInt, int maxInt) {
public void setMinInteger(int minInt) {
// Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
assert minInt >= 0;
assert maxInt >= minInt;
// Special behavior: do not set minInt to be less than what is already set.
// This is so significant digits rounding can set the integer length.
@ -149,30 +119,40 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
lOptPos = maxInt;
lReqPos = minInt;
}
@Override
public void setFractionLength(int minFrac, int maxFrac) {
public void setMinFraction(int minFrac) {
// Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
assert minFrac >= 0;
assert maxFrac >= minFrac;
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
rReqPos = -minFrac;
rOptPos = -maxFrac;
}
@Override
public void applyMaxInteger(int maxInt) {
// Validation should happen outside of DecimalQuantity, e.g., in the Precision class.
assert maxInt >= 0;
if (precision == 0) {
return;
}
int magnitude = getMagnitude();
if (maxInt <= magnitude) {
popFromLeft(magnitude - maxInt + 1);
compact();
}
}
@Override
public long getPositionFingerprint() {
long fingerprint = 0;
fingerprint ^= lOptPos;
fingerprint ^= (lReqPos << 16);
fingerprint ^= ((long) rReqPos << 32);
fingerprint ^= ((long) rOptPos << 48);
return fingerprint;
}
@ -276,7 +256,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
assert !isApproximate;
int magnitude = scale + precision;
int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
int result = (lReqPos > magnitude) ? lReqPos : magnitude;
return result - 1;
}
@ -287,7 +267,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
assert !isApproximate;
int magnitude = scale;
int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
int result = (rReqPos < magnitude) ? rReqPos : magnitude;
return result;
}
@ -586,7 +566,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
// Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
assert(truncateIfOverflow || fitsInLong());
long result = 0L;
int upperMagnitude = Math.min(scale + precision, lOptPos) - 1;
int upperMagnitude = scale + precision - 1;
if (truncateIfOverflow) {
upperMagnitude = Math.min(upperMagnitude, 17);
}
@ -607,7 +587,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
public long toFractionLong(boolean includeTrailingZeros) {
long result = 0L;
int magnitude = -1;
int lowerMagnitude = Math.max(scale, rOptPos);
int lowerMagnitude = scale;
if (includeTrailingZeros) {
lowerMagnitude = Math.min(lowerMagnitude, rReqPos);
}
@ -1055,8 +1035,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
// NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from
// rOptPos (aka -maxFrac) due to overflow.
int upperPos = Math.min(precision + scale, lOptPos) - scale - 1;
int lowerPos = Math.max(scale, rOptPos) - scale;
int upperPos = precision - 1;
int lowerPos = 0;
int p = upperPos;
result.append((char) ('0' + getDigitPos(p)));
if ((--p) >= lowerPos) {
@ -1105,10 +1085,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
scale == _other.scale
&& precision == _other.precision
&& flags == _other.flags
&& lOptPos == _other.lOptPos
&& lReqPos == _other.lReqPos
&& rReqPos == _other.rReqPos
&& rOptPos == _other.rOptPos
&& isApproximate == _other.isApproximate;
if (!basicEquals) {
return false;
@ -1169,6 +1147,14 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
*/
protected abstract void shiftRight(int numDigits);
/**
* Directly removes digits from the front of the BCD list.
* Updates precision.
*
* CAUTION: it is the caller's responsibility to call {@link #compact} after this method.
*/
protected abstract void popFromLeft(int numDigits);
/**
* Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
* origDouble, origDelta, and BCD data.

View file

@ -154,6 +154,19 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
precision -= numDigits;
}
@Override
protected void popFromLeft(int numDigits) {
if (usingBytes) {
int i = precision - 1;
for (; i >= precision - numDigits; i--) {
bcdBytes[i] = 0;
}
} else {
bcdLong &= (1L << ((precision - numDigits) * 4)) - 1;
}
precision -= numDigits;
}
@Override
protected void setBcdToZero() {
if (usingBytes) {
@ -425,11 +438,9 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
@Override
public String toString() {
return String.format("<DecimalQuantity %s:%d:%d:%s %s %s%s>",
(lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
return String.format("<DecimalQuantity %d:%d %s %s%s>",
lReqPos,
rReqPos,
(rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
(usingBytes ? "bytes" : "long"),
(isNegative() ? "-" : ""),
toNumberString());

View file

@ -99,9 +99,10 @@ class NumberFormatterImpl {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
inValue.setMinInteger(micros.integerWidth.minInt);
inValue.applyMaxInteger(micros.integerWidth.maxInt);
}
return micros;
}
@ -111,9 +112,10 @@ class NumberFormatterImpl {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
inValue.setMinInteger(micros.integerWidth.minInt);
inValue.applyMaxInteger(micros.integerWidth.maxInt);
}
return micros;
}

View file

@ -609,7 +609,7 @@ public abstract class Precision implements Cloneable {
@Override
public void apply(DecimalQuantity value) {
value.roundToInfinity();
value.setFractionLength(0, Integer.MAX_VALUE);
value.setMinFraction(0);
}
}
@ -625,8 +625,7 @@ public abstract class Precision implements Cloneable {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)),
Integer.MAX_VALUE);
value.setMinFraction(Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
}
}
@ -642,11 +641,10 @@ public abstract class Precision implements Cloneable {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)),
Integer.MAX_VALUE);
value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
// Make sure that digits are displayed on zero.
if (value.isZero() && minSig > 0) {
value.setIntegerLength(1, Integer.MAX_VALUE);
value.setMinInteger(1);
}
}
@ -656,7 +654,7 @@ public abstract class Precision implements Cloneable {
*/
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZero();
quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
quantity.setMinFraction(minSig - minInt);
}
}
@ -687,7 +685,7 @@ public abstract class Precision implements Cloneable {
roundingMag = Math.min(roundingMag, candidate);
}
value.roundToMagnitude(roundingMag, mathContext);
value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
value.setMinFraction(Math.max(0, -displayMag));
}
}
@ -701,7 +699,7 @@ public abstract class Precision implements Cloneable {
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
value.setFractionLength(increment.scale(), increment.scale());
value.setMinFraction(increment.scale());
}
}

View file

@ -81,6 +81,12 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD
precision -= numDigits;
}
@Override
protected void popFromLeft(int numDigits) {
bcd &= (1L << ((precision - numDigits) * 4)) - 1;
precision -= numDigits;
}
@Override
protected void setBcdToZero() {
bcd = 0L;
@ -173,11 +179,9 @@ public final class DecimalQuantity_64BitBCD extends DecimalQuantity_AbstractBCD
@Override
public String toString() {
return String.format(
"<DecimalQuantity2 %s:%d:%d:%s %016XE%d>",
(lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
"<DecimalQuantity2 %d:%d %016XE%d>",
lReqPos,
rReqPos,
(rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
bcd,
scale);
}

View file

@ -93,6 +93,15 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract
precision -= numDigits;
}
@Override
protected void popFromLeft(int numDigits) {
int i = precision - 1;
for (; i >= precision - numDigits; i--) {
bcd[i] = 0;
}
precision -= numDigits;
}
@Override
protected void setBcdToZero() {
for (int i = 0; i < precision; i++) {
@ -221,11 +230,9 @@ public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_Abstract
sb.append(bcd[i]);
}
return String.format(
"<DecimalQuantity3 %s:%d:%d:%s %s%s%d>",
(lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
"<DecimalQuantity3 %d:%d %s%s%d>",
lReqPos,
rReqPos,
(rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
sb,
"E",
scale);

View file

@ -319,37 +319,39 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
}
@Override
public void setIntegerLength(int minInt, int maxInt) {
public void setMinInteger(int minInt) {
// Graceful failures for bogus input
minInt = Math.max(0, minInt);
maxInt = Math.max(0, maxInt);
// The minima must be less than or equal to the maxima
if (maxInt < minInt) {
minInt = maxInt;
}
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
lOptPos = maxInt;
lReqPos = minInt;
}
@Override
public void setFractionLength(int minFrac, int maxFrac) {
public void setMinFraction(int minFrac) {
// Graceful failures for bogus input
minFrac = Math.max(0, minFrac);
maxFrac = Math.max(0, maxFrac);
// The minima must be less than or equal to the maxima
if (maxFrac < minFrac) {
minFrac = maxFrac;
}
// Save values into internal state
// Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
rReqPos = -minFrac;
rOptPos = -maxFrac;
}
@Override
public void applyMaxInteger(int maxInt) {
BigDecimal d;
if (primary != -1) {
d = BigDecimal.valueOf(primary).scaleByPowerOfTen(primaryScale);
} else {
d = fallback;
}
d = d.scaleByPowerOfTen(-maxInt).remainder(BigDecimal.ONE).scaleByPowerOfTen(maxInt);
if (primary != -1) {
primary = d.scaleByPowerOfTen(-primaryScale).longValueExact();
} else {
fallback = d;
}
}
@Override

View file

@ -168,8 +168,8 @@ public class DecimalQuantityTest extends TestFmwk {
DecimalQuantity q0 = rq.createCopy();
// Force an accurate double
q0.roundToInfinity();
q0.setIntegerLength(1, Integer.MAX_VALUE);
q0.setFractionLength(1, Integer.MAX_VALUE);
q0.setMinInteger(1);
q0.setMinFraction(1);
String actual = q0.toPlainString();
assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
}
@ -305,6 +305,15 @@ public class DecimalQuantityTest extends TestFmwk {
assertFalse("Should not be using byte array", fq.isUsingBytes());
assertEquals("Failed on round", "1.23412341234E+16", fq.toScientificString());
assertNull("Failed health check", fq.checkHealth());
// Bytes with popFromLeft
fq.setToBigDecimal(new BigDecimal("999999999999999999"));
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 999999999999999999E0>");
fq.applyMaxInteger(17);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 99999999999999999E0>");
fq.applyMaxInteger(16);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 9999999999999999E0>");
fq.applyMaxInteger(15);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 999999999999999E0>");
}
@Test
@ -389,25 +398,26 @@ public class DecimalQuantityTest extends TestFmwk {
@Test
public void testDecimalQuantityBehaviorStandalone() {
DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 0E0>");
fq.setToInt(51423);
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E0>");
fq.adjustMagnitude(-3);
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
fq.setToLong(999999999999000L);
assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
fq.setIntegerLength(2, 5);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
fq.setFractionLength(3, 6);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E-3>");
fq.setToLong(90909090909000L);
assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 90909090909E3>");
fq.setMinInteger(2);
fq.applyMaxInteger(5);
assertToStringAndHealth(fq, "<DecimalQuantity 2:0 long 9E3>");
fq.setMinFraction(3);
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 9E3>");
fq.setToDouble(987.654321);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToInfinity();
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987655E-3>");
fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 98766E-2>");
}
@Test
@ -501,8 +511,10 @@ public class DecimalQuantityTest extends TestFmwk {
public void testMaxDigits() {
DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(876.543);
dq.roundToInfinity();
dq.setIntegerLength(0, 2);
dq.setFractionLength(0, 2);
dq.setMinInteger(0);
dq.applyMaxInteger(2);
dq.setMinFraction(0);
dq.roundToMagnitude(-2, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
assertEquals("Should trim, toLong", 76, dq.toLong(true));