Project

General

Profile

Backport #3803 » ruby_bigdecimal_round_half.patch

Minimal patch with test and docfix - matthw (Matthew Willson), 09/15/2010 10:56 AM

View differences:

ext/bigdecimal/bigdecimal.c (working copy)
*
* ROUND_UP:: round away from zero
* ROUND_DOWN:: round towards zero (truncate)
* ROUND_HALF_UP:: round up if the appropriate digit >= 5, otherwise truncate (default)
* ROUND_HALF_DOWN:: round up if the appropriate digit >= 6, otherwise truncate
* ROUND_HALF_EVEN:: round towards the even neighbor (Banker's rounding)
* ROUND_HALF_UP:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round away from zero. (default)
* ROUND_HALF_DOWN:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards zero.
* ROUND_HALF_EVEN:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards the even neighbor (Banker's rounding)
* ROUND_CEILING:: round towards positive infinity (ceil)
* ROUND_FLOOR:: round towards negative infinity (floor)
*
......
{
/* fracf: any positive digit under rounding position? */
/* exptoadd: number of digits needed to compensate negative nf */
int fracf;
int fracf, fracf_1further;
ssize_t n,i,ix,ioffset, exptoadd;
BDIGIT v, shifter;
BDIGIT div;
......
n = (ssize_t)BASE_FIG - ioffset - 1;
for (shifter=1,i=0; i<n; ++i) shifter *= 10;
fracf = (v % (shifter * 10) > 0);
fracf_1further = ((v % shifter) > 0);
v /= shifter;
div = v / 10;
v = v - div*10;
if (fracf == 0) {
for (i=ix+1; (size_t)i < y->Prec; i++) {
if (y->frac[i] % BASE) {
fracf = 1;
break;
}
for (i=ix+1; (size_t)i < y->Prec; i++) {
if (y->frac[i] % BASE) {
fracf = fracf_1further = 1;
break;
}
}
memset(y->frac+ix+1, 0, (y->Prec - (ix+1)) * sizeof(BDIGIT));
......
if (v>=5) ++div;
break;
case VP_ROUND_HALF_DOWN: /* Round half down */
if (v>=6) ++div;
if (v>5 || (v == 5 && fracf_1further)) ++div;
break;
case VP_ROUND_CEIL: /* ceil */
if (fracf && (VpGetSign(y)>0)) ++div;
......
case VP_ROUND_HALF_EVEN: /* Banker's rounding */
if (v>5) ++div;
else if (v==5) {
if ((size_t)i == (BASE_FIG-1)) {
if (ix && (y->frac[ix-1]%2)) ++div;
if (fracf_1further) {
++div;
}
else {
if (div%2) ++div;
else {
if (ioffset == 0) {
if (ix && (y->frac[ix-1]%2)) ++div;
}
else {
if (div%2) ++div;
}
}
}
break;
test/bigdecimal/test_bigdecimal.rb (working copy)
assert_equal(3, x.round(0, BigDecimal::ROUND_CEILING))
assert_equal(2, x.round(0, BigDecimal::ROUND_FLOOR))
assert_raise(TypeError) { x.round(0, 256) }
15.times do |n|
x = BigDecimal.new("5#{'0'*n}1")
assert_equal(10**(n+2), x.round(-(n+2), BigDecimal::ROUND_HALF_DOWN))
assert_equal(10**(n+2), x.round(-(n+2), BigDecimal::ROUND_HALF_EVEN))
x = BigDecimal.new("0.5#{'0'*n}1")
assert_equal(1, x.round(0, BigDecimal::ROUND_HALF_DOWN))
assert_equal(1, x.round(0, BigDecimal::ROUND_HALF_EVEN))
x = BigDecimal.new("-0.5#{'0'*n}1")
assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_DOWN))
assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_EVEN))
end
end
def test_truncate
(1-1/2)