Combined ICU4C and ICU4J.
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;
}
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.
}
// 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;
}
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;
}
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;
}
// 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);
}
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);
}
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) {
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);
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;
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()),
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.
*/
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
*/
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.
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);
}
}
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:
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;
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;
}
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:
// 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 */
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() {
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() {
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)
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>");
}
}
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));
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() {
*/
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.
*/
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;
}
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.
}
// 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;
}
assert !isApproximate;
int magnitude = scale + precision;
- int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
+ int result = (lReqPos > magnitude) ? lReqPos : magnitude;
return result - 1;
}
assert !isApproximate;
int magnitude = scale;
- int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
+ int result = (rReqPos < magnitude) ? rReqPos : magnitude;
return result;
}
// 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);
}
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);
}
}
// 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) {
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;
*/
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.
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) {
@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());
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;
}
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;
}
@Override
public void apply(DecimalQuantity value) {
value.roundToInfinity();
- value.setFractionLength(0, Integer.MAX_VALUE);
+ value.setMinFraction(0);
}
}
@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)));
}
}
@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);
}
}
*/
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZero();
- quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
+ quantity.setMinFraction(minSig - minInt);
}
}
roundingMag = Math.min(roundingMag, candidate);
}
value.roundToMagnitude(roundingMag, mathContext);
- value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
+ value.setMinFraction(Math.max(0, -displayMag));
}
}
@Override
public void apply(DecimalQuantity value) {
value.roundToIncrement(increment, mathContext);
- value.setFractionLength(increment.scale(), increment.scale());
+ value.setMinFraction(increment.scale());
}
}
precision -= numDigits;
}
+ @Override
+ protected void popFromLeft(int numDigits) {
+ bcd &= (1L << ((precision - numDigits) * 4)) - 1;
+ precision -= numDigits;
+ }
+
@Override
protected void setBcdToZero() {
bcd = 0L;
@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);
}
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++) {
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);
}
@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
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);
}
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
@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
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));