Avoids expensive arithmetic when performing nickel rounding for currencies such as CAD, CHF, and DKK.
void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode,
int32_t maxFrac, UErrorCode& status) {
+ // TODO(13701): Move the nickel check into a higher-level API.
+ if (roundingIncrement == 0.05) {
+ roundToMagnitude(-2, roundingMode, true, status);
+ roundToMagnitude(-maxFrac, roundingMode, false, status);
+ return;
+ } else if (roundingIncrement == 0.5) {
+ roundToMagnitude(-1, roundingMode, true, status);
+ roundToMagnitude(-maxFrac, roundingMode, false, status);
+ return;
+ }
// TODO(13701): This is innefficient. Improve?
// TODO(13701): Should we convert to decNumber instead?
roundToInfinity();
}
}
+void DecimalQuantity::roundToNickel(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status) {
+ roundToMagnitude(magnitude, roundingMode, true, status);
+}
+
void DecimalQuantity::roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status) {
+ roundToMagnitude(magnitude, roundingMode, false, status);
+}
+
+void DecimalQuantity::roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, bool nickel, UErrorCode& status) {
// The position in the BCD at which rounding will be performed; digits to the right of position
// will be rounded away.
- // TODO: Andy: There was a test failure because of integer overflow here. Should I do
- // "safe subtraction" everywhere in the code? What's the nicest way to do it?
int position = safeSubtract(magnitude, scale);
- if (position <= 0 && !isApproximate) {
+ // "trailing" = least significant digit to the left of rounding
+ int8_t trailingDigit = getDigitPos(position);
+
+ if (position <= 0 && !isApproximate && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
// All digits are to the left of the rounding magnitude.
} else if (precision == 0) {
// No rounding for zero.
} else {
// Perform rounding logic.
// "leading" = most significant digit to the right of rounding
- // "trailing" = least significant digit to the left of rounding
int8_t leadingDigit = getDigitPos(safeSubtract(position, 1));
- int8_t trailingDigit = getDigitPos(position);
// Compute which section of the number we are in.
// EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
// LOWER means we are between the bottom edge and the midpoint, like 1.391
// MIDPOINT means we are exactly in the middle, like 1.500
// UPPER means we are between the midpoint and the top edge, like 1.916
- roundingutils::Section section = roundingutils::SECTION_MIDPOINT;
+ roundingutils::Section section;
if (!isApproximate) {
- if (leadingDigit < 5) {
+ if (nickel && trailingDigit != 2 && trailingDigit != 7) {
+ // Nickel rounding, and not at .02x or .07x
+ if (trailingDigit < 2) {
+ // .00, .01 => down to .00
+ section = roundingutils::SECTION_LOWER;
+ } else if (trailingDigit < 5) {
+ // .03, .04 => up to .05
+ section = roundingutils::SECTION_UPPER;
+ } else if (trailingDigit < 7) {
+ // .05, .06 => down to .05
+ section = roundingutils::SECTION_LOWER;
+ } else {
+ // .08, .09 => up to .10
+ section = roundingutils::SECTION_UPPER;
+ }
+ } else if (leadingDigit < 5) {
+ // Includes nickel rounding .020-.024 and .070-.074
section = roundingutils::SECTION_LOWER;
} else if (leadingDigit > 5) {
+ // Includes nickel rounding .026-.029 and .076-.079
section = roundingutils::SECTION_UPPER;
} else {
+ // Includes nickel rounding .025 and .075
+ section = roundingutils::SECTION_MIDPOINT;
for (int p = safeSubtract(position, 2); p >= 0; p--) {
if (getDigitPos(p) != 0) {
section = roundingutils::SECTION_UPPER;
} else {
int32_t p = safeSubtract(position, 2);
int32_t minP = uprv_max(0, precision - 14);
- if (leadingDigit == 0) {
+ if (leadingDigit == 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
section = roundingutils::SECTION_LOWER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 0) {
break;
}
}
- } else if (leadingDigit == 4) {
+ } else if (leadingDigit == 4 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
+ section = roundingutils::SECTION_MIDPOINT;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
section = roundingutils::SECTION_LOWER;
break;
}
}
- } else if (leadingDigit == 5) {
+ } else if (leadingDigit == 5 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
+ section = roundingutils::SECTION_MIDPOINT;
for (; p >= minP; p--) {
if (getDigitPos(p) != 0) {
section = roundingutils::SECTION_UPPER;
break;
}
}
- } else if (leadingDigit == 9) {
+ } else if (leadingDigit == 9 && (!nickel || trailingDigit == 4 || trailingDigit == 9)) {
section = roundingutils::SECTION_UPPER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
break;
}
}
+ } else if (nickel && trailingDigit != 2 && trailingDigit != 7) {
+ // Nickel rounding, and not at .02x or .07x
+ if (trailingDigit < 2) {
+ // .00, .01 => down to .00
+ section = roundingutils::SECTION_LOWER;
+ } else if (trailingDigit < 5) {
+ // .03, .04 => up to .05
+ section = roundingutils::SECTION_UPPER;
+ } else if (trailingDigit < 7) {
+ // .05, .06 => down to .05
+ section = roundingutils::SECTION_LOWER;
+ } else {
+ // .08, .09 => up to .10
+ section = roundingutils::SECTION_UPPER;
+ }
} else if (leadingDigit < 5) {
+ // Includes nickel rounding .020-.024 and .070-.074
section = roundingutils::SECTION_LOWER;
} else {
+ // Includes nickel rounding .026-.029 and .076-.079
section = roundingutils::SECTION_UPPER;
}
if (safeSubtract(position, 1) < precision - 14 ||
(roundsAtMidpoint && section == roundingutils::SECTION_MIDPOINT) ||
(!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
- // Oops! This means that we have to get the exact representation of the double, because
- // the zone of uncertainty is along the rounding boundary.
+ // Oops! This means that we have to get the exact representation of the double,
+ // because the zone of uncertainty is along the rounding boundary.
convertToAccurateDouble();
- roundToMagnitude(magnitude, roundingMode, status); // start over
+ roundToMagnitude(magnitude, roundingMode, nickel, status); // start over
return;
}
origDouble = 0.0;
origDelta = 0;
- if (position <= 0) {
+ if (position <= 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
// All digits are to the left of the rounding magnitude.
return;
}
if (section == -2) { section = roundingutils::SECTION_UPPER; }
}
- bool roundDown = roundingutils::getRoundingDirection((trailingDigit % 2) == 0,
+ // Nickel rounding "half even" goes to the nearest whole (away from the 5).
+ bool isEven = nickel
+ ? (trailingDigit < 2 || trailingDigit > 7
+ || (trailingDigit == 2 && section != roundingutils::SECTION_UPPER)
+ || (trailingDigit == 7 && section == roundingutils::SECTION_UPPER))
+ : (trailingDigit % 2) == 0;
+
+ bool roundDown = roundingutils::getRoundingDirection(isEven,
isNegative(),
section,
roundingMode,
shiftRight(position);
}
+ if (nickel) {
+ if (trailingDigit < 5 && roundDown) {
+ setDigitPos(0, 0);
+ compact();
+ return;
+ } else if (trailingDigit >= 5 && !roundDown) {
+ setDigitPos(0, 9);
+ trailingDigit = 9;
+ // do not return: use the bubbling logic below
+ } else {
+ setDigitPos(0, 5);
+ // compact not necessary: digit at position 0 is nonzero
+ return;
+ }
+ }
+
// Bubble the result to the higher digits
if (!roundDown) {
if (trailingDigit == 9) {
int bubblePos = 0;
- // Note: in the long implementation, the most digits BCD can have at this point is 15,
- // so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
+ // Note: in the long implementation, the most digits BCD can have at this point is
+ // 15, so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
for (; getDigitPos(bubblePos) == 9; bubblePos++) {}
shiftRight(bubblePos); // shift off the trailing 9s
}
* <p>If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
*
* @param roundingIncrement The increment to which to round.
- * @param mathContext The {@link RoundingMode} to use if rounding is necessary.
+ * @param roundingMode The {@link RoundingMode} to use if rounding is necessary.
*/
void roundToIncrement(double roundingIncrement, RoundingMode roundingMode,
int32_t maxFrac, UErrorCode& status);
/** Removes all fraction digits. */
void truncate();
+ /**
+ * Rounds the number to the nearest multiple of 5 at the specified magnitude.
+ * For example, when magnitude == -2, this performs rounding to the nearest 0.05.
+ *
+ * @param magnitude The magnitude at which the digit should become either 0 or 5.
+ * @param roundingMode Rounding strategy.
+ */
+ void roundToNickel(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status);
+
/**
* Rounds the number to a specified magnitude (power of ten).
*
* @param roundingMagnitude The power of ten to which to round. For example, a value of -2 will
* round to 2 decimal places.
- * @param mathContext The {@link RoundingMode} to use if rounding is necessary.
+ * @param roundingMode The {@link RoundingMode} to use if rounding is necessary.
*/
void roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status);
*/
bool explicitExactDouble = false;
+ void roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, bool nickel, UErrorCode& status);
+
/**
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
*
void testHardDoubleConversion();
void testToDouble();
void testMaxDigits();
+ void testNickelRounding();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
TESTCASE_AUTO(testHardDoubleConversion);
TESTCASE_AUTO(testToDouble);
TESTCASE_AUTO(testMaxDigits);
+ TESTCASE_AUTO(testNickelRounding);
TESTCASE_AUTO_END;
}
}
}
+void DecimalQuantityTest::testNickelRounding() {
+ IcuTestErrorCode status(*this, "testNickelRounding");
+ struct TestCase {
+ double input;
+ int32_t magnitude;
+ UNumberFormatRoundingMode roundingMode;
+ const char16_t* expected;
+ } cases[] = {
+ {1.000, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.001, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.010, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.020, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.024, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.025, -2, UNUM_ROUND_HALFEVEN, u"1"},
+ {1.025, -2, UNUM_ROUND_HALFDOWN, u"1"},
+ {1.025, -2, UNUM_ROUND_HALFUP, u"1.05"},
+ {1.026, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.030, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.040, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.050, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.060, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.070, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.074, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
+ {1.075, -2, UNUM_ROUND_HALFDOWN, u"1.05"},
+ {1.075, -2, UNUM_ROUND_HALFUP, u"1.1"},
+ {1.075, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
+ {1.076, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
+ {1.080, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
+ {1.090, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
+ {1.099, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
+ {1.999, -2, UNUM_ROUND_HALFEVEN, u"2"},
+ {2.25, -1, UNUM_ROUND_HALFEVEN, u"2"},
+ {2.25, -1, UNUM_ROUND_HALFUP, u"2.5"},
+ {2.75, -1, UNUM_ROUND_HALFDOWN, u"2.5"},
+ {2.75, -1, UNUM_ROUND_HALFEVEN, u"3"},
+ {3.00, -1, UNUM_ROUND_CEILING, u"3"},
+ {3.25, -1, UNUM_ROUND_CEILING, u"3.5"},
+ {3.50, -1, UNUM_ROUND_CEILING, u"3.5"},
+ {3.75, -1, UNUM_ROUND_CEILING, u"4"},
+ {4.00, -1, UNUM_ROUND_FLOOR, u"4"},
+ {4.25, -1, UNUM_ROUND_FLOOR, u"4"},
+ {4.50, -1, UNUM_ROUND_FLOOR, u"4.5"},
+ {4.75, -1, UNUM_ROUND_FLOOR, u"4.5"},
+ {5.00, -1, UNUM_ROUND_UP, u"5"},
+ {5.25, -1, UNUM_ROUND_UP, u"5.5"},
+ {5.50, -1, UNUM_ROUND_UP, u"5.5"},
+ {5.75, -1, UNUM_ROUND_UP, u"6"},
+ {6.00, -1, UNUM_ROUND_DOWN, u"6"},
+ {6.25, -1, UNUM_ROUND_DOWN, u"6"},
+ {6.50, -1, UNUM_ROUND_DOWN, u"6.5"},
+ {6.75, -1, UNUM_ROUND_DOWN, u"6.5"},
+ {7.00, -1, UNUM_ROUND_UNNECESSARY, u"7"},
+ {7.50, -1, UNUM_ROUND_UNNECESSARY, u"7.5"},
+ };
+ for (const auto& cas : cases) {
+ UnicodeString message = DoubleToUnicodeString(cas.input) + u" @ " + Int64ToUnicodeString(cas.magnitude) + u" / " + Int64ToUnicodeString(cas.roundingMode);
+ status.setScope(message);
+ DecimalQuantity dq;
+ dq.setToDouble(cas.input);
+ dq.roundToNickel(cas.magnitude, cas.roundingMode, status);
+ status.errIfFailureAndReset();
+ UnicodeString actual = dq.toPlainString();
+ assertEquals(message, cas.expected, actual);
+ }
+ status.setScope("");
+ DecimalQuantity dq;
+ dq.setToDouble(7.1);
+ dq.roundToNickel(-1, UNUM_ROUND_UNNECESSARY, status);
+ status.expectErrorAndReset(U_FORMAT_INEXACT_ERROR);
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
*/
public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
+ /**
+ * Rounds the number to the nearest multiple of 5 at the specified magnitude.
+ * For example, when magnitude == -2, this performs rounding to the nearest 0.05.
+ *
+ * @param magnitude
+ * The magnitude at which the digit should become either 0 or 5.
+ * @param mathContext
+ * Rounding strategy.
+ */
+ public void roundToNickel(int magnitude, MathContext mathContext);
+
/**
* Rounds the number to a specified magnitude (power of ten).
*
@Override
public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) {
- // TODO: Avoid converting back and forth to BigDecimal.
+ // TODO(13701): Avoid this check on every call to roundToIncrement().
+ BigDecimal stripped = roundingIncrement.stripTrailingZeros();
+ if (stripped.unscaledValue().compareTo(BigInteger.valueOf(5)) == 0) {
+ roundToNickel(-stripped.scale(), mathContext);
+ return;
+ }
BigDecimal temp = toBigDecimal();
temp = temp.divide(roundingIncrement, 0, mathContext.getRoundingMode())
.multiply(roundingIncrement).round(mathContext);
}
}
+ @Override
+ public void roundToNickel(int magnitude, MathContext mathContext) {
+ roundToMagnitude(magnitude, mathContext, true);
+ }
+
@Override
public void roundToMagnitude(int magnitude, MathContext mathContext) {
+ roundToMagnitude(magnitude, mathContext, false);
+ }
+
+ private void roundToMagnitude(int magnitude, MathContext mathContext, boolean nickel) {
// The position in the BCD at which rounding will be performed; digits to the right of position
// will be rounded away.
- // TODO: Andy: There was a test failure because of integer overflow here. Should I do
- // "safe subtraction" everywhere in the code? What's the nicest way to do it?
int position = safeSubtract(magnitude, scale);
// Enforce the number of digits required by the MathContext.
int _mcPrecision = mathContext.getPrecision();
- if (magnitude == Integer.MAX_VALUE
- || (_mcPrecision > 0 && precision - position > _mcPrecision)) {
+ if (_mcPrecision > 0 && precision - _mcPrecision > position) {
position = precision - _mcPrecision;
}
- if (position <= 0 && !isApproximate) {
+ // "trailing" = least significant digit to the left of rounding
+ byte trailingDigit = getDigitPos(position);
+
+ if (position <= 0 && !isApproximate && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
// All digits are to the left of the rounding magnitude.
} else if (precision == 0) {
// No rounding for zero.
} else {
// Perform rounding logic.
// "leading" = most significant digit to the right of rounding
- // "trailing" = least significant digit to the left of rounding
byte leadingDigit = getDigitPos(safeSubtract(position, 1));
- byte trailingDigit = getDigitPos(position);
// Compute which section of the number we are in.
// EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
// LOWER means we are between the bottom edge and the midpoint, like 1.391
// MIDPOINT means we are exactly in the middle, like 1.500
// UPPER means we are between the midpoint and the top edge, like 1.916
- int section = RoundingUtils.SECTION_MIDPOINT;
+ int section;
if (!isApproximate) {
- if (leadingDigit < 5) {
+ if (nickel && trailingDigit != 2 && trailingDigit != 7) {
+ // Nickel rounding, and not at .02x or .07x
+ if (trailingDigit < 2) {
+ // .00, .01 => down to .00
+ section = RoundingUtils.SECTION_LOWER;
+ } else if (trailingDigit < 5) {
+ // .03, .04 => up to .05
+ section = RoundingUtils.SECTION_UPPER;
+ } else if (trailingDigit < 7) {
+ // .05, .06 => down to .05
+ section = RoundingUtils.SECTION_LOWER;
+ } else {
+ // .08, .09 => up to .10
+ section = RoundingUtils.SECTION_UPPER;
+ }
+ } else if (leadingDigit < 5) {
+ // Includes nickel rounding .020-.024 and .070-.074
section = RoundingUtils.SECTION_LOWER;
} else if (leadingDigit > 5) {
+ // Includes nickel rounding .026-.029 and .076-.079
section = RoundingUtils.SECTION_UPPER;
} else {
+ // Includes nickel rounding .025 and .075
+ section = RoundingUtils.SECTION_MIDPOINT;
for (int p = safeSubtract(position, 2); p >= 0; p--) {
if (getDigitPos(p) != 0) {
section = RoundingUtils.SECTION_UPPER;
} else {
int p = safeSubtract(position, 2);
int minP = Math.max(0, precision - 14);
- if (leadingDigit == 0) {
+ if (leadingDigit == 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
section = SECTION_LOWER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 0) {
break;
}
}
- } else if (leadingDigit == 4) {
+ } else if (leadingDigit == 4 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
+ section = RoundingUtils.SECTION_MIDPOINT;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
section = RoundingUtils.SECTION_LOWER;
break;
}
}
- } else if (leadingDigit == 5) {
+ } else if (leadingDigit == 5 && (!nickel || trailingDigit == 2 || trailingDigit == 7)) {
+ section = RoundingUtils.SECTION_MIDPOINT;
for (; p >= minP; p--) {
if (getDigitPos(p) != 0) {
section = RoundingUtils.SECTION_UPPER;
break;
}
}
- } else if (leadingDigit == 9) {
+ } else if (leadingDigit == 9 && (!nickel || trailingDigit == 4 || trailingDigit == 9)) {
section = SECTION_UPPER_EDGE;
for (; p >= minP; p--) {
if (getDigitPos(p) != 9) {
break;
}
}
+ } else if (nickel && trailingDigit != 2 && trailingDigit != 7) {
+ // Nickel rounding, and not at .02x or .07x
+ if (trailingDigit < 2) {
+ // .00, .01 => down to .00
+ section = RoundingUtils.SECTION_LOWER;
+ } else if (trailingDigit < 5) {
+ // .03, .04 => up to .05
+ section = RoundingUtils.SECTION_UPPER;
+ } else if (trailingDigit < 7) {
+ // .05, .06 => down to .05
+ section = RoundingUtils.SECTION_LOWER;
+ } else {
+ // .08, .09 => up to .10
+ section = RoundingUtils.SECTION_UPPER;
+ }
} else if (leadingDigit < 5) {
+ // Includes nickel rounding .020-.024 and .070-.074
section = RoundingUtils.SECTION_LOWER;
} else {
+ // Includes nickel rounding .026-.029 and .076-.079
section = RoundingUtils.SECTION_UPPER;
}
|| (roundsAtMidpoint && section == RoundingUtils.SECTION_MIDPOINT)
|| (!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
// Oops! This means that we have to get the exact representation of the double,
- // because
- // the zone of uncertainty is along the rounding boundary.
+ // because the zone of uncertainty is along the rounding boundary.
convertToAccurateDouble();
- roundToMagnitude(magnitude, mathContext); // start over
+ roundToMagnitude(magnitude, mathContext, nickel); // start over
return;
}
origDouble = 0.0;
origDelta = 0;
- if (position <= 0) {
+ if (position <= 0 && (!nickel || trailingDigit == 0 || trailingDigit == 5)) {
// All digits are to the left of the rounding magnitude.
return;
}
section = RoundingUtils.SECTION_UPPER;
}
- boolean roundDown = RoundingUtils.getRoundingDirection((trailingDigit % 2) == 0,
+ // Nickel rounding "half even" goes to the nearest whole (away from the 5).
+ boolean isEven = nickel
+ ? (trailingDigit < 2 || trailingDigit > 7
+ || (trailingDigit == 2 && section != RoundingUtils.SECTION_UPPER)
+ || (trailingDigit == 7 && section == RoundingUtils.SECTION_UPPER))
+ : (trailingDigit % 2) == 0;
+
+ boolean roundDown = RoundingUtils.getRoundingDirection(isEven,
isNegative(),
section,
mathContext.getRoundingMode().ordinal(),
shiftRight(position);
}
+ if (nickel) {
+ if (trailingDigit < 5 && roundDown) {
+ setDigitPos(0, (byte) 0);
+ compact();
+ return;
+ } else if (trailingDigit >= 5 && !roundDown) {
+ setDigitPos(0, (byte) 9);
+ trailingDigit = 9;
+ // do not return: use the bubbling logic below
+ } else {
+ setDigitPos(0, (byte) 5);
+ // compact not necessary: digit at position 0 is nonzero
+ return;
+ }
+ }
+
// Bubble the result to the higher digits
if (!roundDown) {
if (trailingDigit == 9) {
int bubblePos = 0;
// Note: in the long implementation, the most digits BCD can have at this point is
- // 15,
- // so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
+ // 15, so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
for (; getDigitPos(bubblePos) == 9; bubblePos++) {
}
shiftRight(bubblePos); // shift off the trailing 9s
primary = -1;
}
+ @Override
+ public void roundToNickel(int roundingMagnitude, MathContext mathContext) {
+ BigDecimal nickel = BigDecimal.valueOf(5).scaleByPowerOfTen(roundingMagnitude);
+ roundToIncrement(nickel, mathContext);
+ }
+
@Override
public void roundToMagnitude(int roundingMagnitude, MathContext mathContext) {
if (roundingMagnitude < -1000) {
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
public void testBehavior() throws ParseException {
// Make a list of several formatters to test the behavior of DecimalQuantity.
- List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
+ List<LocalizedNumberFormatter> formats = new ArrayList<>();
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
}
- List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
+ List<DecimalQuantity> qs = new ArrayList<>();
BigDecimal d = new BigDecimal(str);
qs.add(new DecimalQuantity_SimpleStorage(d));
if (mode == 0)
}
}
+ @Test
+ public void testNickelRounding() {
+ Object[][] cases = new Object[][] {
+ {1.000, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.001, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.010, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.020, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.024, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.025, -2, RoundingMode.HALF_EVEN, "1."},
+ {1.025, -2, RoundingMode.HALF_DOWN, "1."},
+ {1.025, -2, RoundingMode.HALF_UP, "1.05"},
+ {1.026, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.030, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.040, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.050, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.060, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.070, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.074, -2, RoundingMode.HALF_EVEN, "1.05"},
+ {1.075, -2, RoundingMode.HALF_DOWN, "1.05"},
+ {1.075, -2, RoundingMode.HALF_UP, "1.1"},
+ {1.075, -2, RoundingMode.HALF_EVEN, "1.1"},
+ {1.076, -2, RoundingMode.HALF_EVEN, "1.1"},
+ {1.080, -2, RoundingMode.HALF_EVEN, "1.1"},
+ {1.090, -2, RoundingMode.HALF_EVEN, "1.1"},
+ {1.099, -2, RoundingMode.HALF_EVEN, "1.1"},
+ {1.999, -2, RoundingMode.HALF_EVEN, "2."},
+ {2.25, -1, RoundingMode.HALF_EVEN, "2."},
+ {2.25, -1, RoundingMode.HALF_UP, "2.5"},
+ {2.75, -1, RoundingMode.HALF_DOWN, "2.5"},
+ {2.75, -1, RoundingMode.HALF_EVEN, "3."},
+ {3.00, -1, RoundingMode.CEILING, "3."},
+ {3.25, -1, RoundingMode.CEILING, "3.5"},
+ {3.50, -1, RoundingMode.CEILING, "3.5"},
+ {3.75, -1, RoundingMode.CEILING, "4."},
+ {4.00, -1, RoundingMode.FLOOR, "4."},
+ {4.25, -1, RoundingMode.FLOOR, "4."},
+ {4.50, -1, RoundingMode.FLOOR, "4.5"},
+ {4.75, -1, RoundingMode.FLOOR, "4.5"},
+ {5.00, -1, RoundingMode.UP, "5."},
+ {5.25, -1, RoundingMode.UP, "5.5"},
+ {5.50, -1, RoundingMode.UP, "5.5"},
+ {5.75, -1, RoundingMode.UP, "6."},
+ {6.00, -1, RoundingMode.DOWN, "6."},
+ {6.25, -1, RoundingMode.DOWN, "6."},
+ {6.50, -1, RoundingMode.DOWN, "6.5"},
+ {6.75, -1, RoundingMode.DOWN, "6.5"},
+ {7.00, -1, RoundingMode.UNNECESSARY, "7."},
+ {7.50, -1, RoundingMode.UNNECESSARY, "7.5"},
+ };
+ for (Object[] cas : cases) {
+ double input = (Double) cas[0];
+ int magnitude = (Integer) cas[1];
+ RoundingMode roundingMode = (RoundingMode) cas[2];
+ String expected = (String) cas[3];
+ String message = input + " @ " + magnitude + " / " + roundingMode;
+ for (int i=0; i<2; i++) {
+ DecimalQuantity dq;
+ if (i == 0) {
+ dq = new DecimalQuantity_DualStorageBCD(input);
+ } else {
+ dq = new DecimalQuantity_SimpleStorage(input);
+ }
+ dq.roundToNickel(magnitude, RoundingUtils.mathContextUnlimited(roundingMode));
+ String actual = dq.toPlainString();
+ assertEquals(message, expected, actual);
+ }
+ }
+ try {
+ DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(7.1);
+ dq.roundToNickel(-1, RoundingUtils.mathContextUnlimited(RoundingMode.UNNECESSARY));
+ fail("Expected ArithmeticException");
+ } catch (ArithmeticException expected) {
+ // pass
+ }
+ }
+
static void assertDoubleEquals(String message, double d1, double d2) {
boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
handleAssert(equal, message, d1, d2, null, false);