1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* ext/bigdecimal/bigdecimal.c (check_rounding_mode): added for

converting symbol to rounding mode number.
* ext/bigdecimal/bigdecimal.c (BigDecimal_mode, BigDecimal_round):
  support to specify rounding mode by symbol.
* test/bigdecimal/test_bigdecimal.rb (test_mode, test_round):
  add tests for avobe changes.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@29295 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
mrkn 2010-09-18 21:06:08 +00:00
parent d6e9eebd74
commit fb7fe14575
3 changed files with 155 additions and 67 deletions

View file

@ -1,3 +1,14 @@
Sun Sep 19 06:06:07 2010 Kenta Murata <mrkn@mrkn.jp>
* ext/bigdecimal/bigdecimal.c (check_rounding_mode): added for
converting symbol to rounding mode number.
* ext/bigdecimal/bigdecimal.c (BigDecimal_mode, BigDecimal_round):
support to specify rounding mode by symbol.
* test/bigdecimal/test_bigdecimal.rb (test_mode, test_round):
add tests for avobe changes.
Sun Sep 19 05:14:35 2010 Kenta Murata <mrkn@mrkn.jp>
* ext/bigdecimal/bigdecimal.c: fix rounding algorithms for half-down

View file

@ -36,6 +36,18 @@ static ID id_BigDecimal_exception_mode;
static ID id_BigDecimal_rounding_mode;
static ID id_BigDecimal_precision_limit;
static ID id_up;
static ID id_down;
static ID id_truncate;
static ID id_half_up;
static ID id_default;
static ID id_half_down;
static ID id_half_even;
static ID id_banker;
static ID id_ceiling;
static ID id_ceil;
static ID id_floor;
/* MACRO's to guard objects from GC by keeping them in stack */
#define ENTER(n) volatile VALUE vStack[n];int iStack=0
#define PUSH(x) vStack[iStack++] = (unsigned long)(x);
@ -277,44 +289,80 @@ BigDecimal_load(VALUE self, VALUE str)
return ToValue(pv);
}
/* call-seq:
* BigDecimal.mode(mode, value)
*
* Controls handling of arithmetic exceptions and rounding. If no value
* is supplied, the current value is returned.
*
* Six values of the mode parameter control the handling of arithmetic
* exceptions:
*
* BigDecimal::EXCEPTION_NaN
* BigDecimal::EXCEPTION_INFINITY
* BigDecimal::EXCEPTION_UNDERFLOW
* BigDecimal::EXCEPTION_OVERFLOW
* BigDecimal::EXCEPTION_ZERODIVIDE
* BigDecimal::EXCEPTION_ALL
*
* For each mode parameter above, if the value set is false, computation
* continues after an arithmetic exception of the appropriate type.
* When computation continues, results are as follows:
*
* EXCEPTION_NaN:: NaN
* EXCEPTION_INFINITY:: +infinity or -infinity
* EXCEPTION_UNDERFLOW:: 0
* EXCEPTION_OVERFLOW:: +infinity or -infinity
* EXCEPTION_ZERODIVIDE:: +infinity or -infinity
*
* One value of the mode parameter controls the rounding of numeric values:
* BigDecimal::ROUND_MODE. The values it can take are:
*
* ROUND_UP:: round away from zero
* ROUND_DOWN:: round towards zero (truncate)
* 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)
*
*/
static unsigned short
check_rounding_mode(VALUE const v)
{
unsigned short sw;
ID id;
switch (TYPE(v)) {
case T_SYMBOL:
id = SYM2ID(v);
if (id == id_up)
return VP_ROUND_UP;
if (id == id_down || id == id_truncate)
return VP_ROUND_DOWN;
if (id == id_half_up || id == id_default)
return VP_ROUND_HALF_UP;
if (id == id_half_down)
return VP_ROUND_HALF_DOWN;
if (id == id_half_even || id == id_banker)
return VP_ROUND_HALF_EVEN;
if (id == id_ceiling || id == id_ceil)
return VP_ROUND_CEIL;
if (id == id_floor)
return VP_ROUND_FLOOR;
break;
default:
break;
}
Check_Type(v, T_FIXNUM);
sw = (unsigned short)FIX2UINT(v);
if (!VpIsRoundMode(sw)) {
rb_raise(rb_eTypeError, "invalid rounding mode");
}
return sw;
}
/* call-seq:
* BigDecimal.mode(mode, value)
*
* Controls handling of arithmetic exceptions and rounding. If no value
* is supplied, the current value is returned.
*
* Six values of the mode parameter control the handling of arithmetic
* exceptions:
*
* BigDecimal::EXCEPTION_NaN
* BigDecimal::EXCEPTION_INFINITY
* BigDecimal::EXCEPTION_UNDERFLOW
* BigDecimal::EXCEPTION_OVERFLOW
* BigDecimal::EXCEPTION_ZERODIVIDE
* BigDecimal::EXCEPTION_ALL
*
* For each mode parameter above, if the value set is false, computation
* continues after an arithmetic exception of the appropriate type.
* When computation continues, results are as follows:
*
* EXCEPTION_NaN:: NaN
* EXCEPTION_INFINITY:: +infinity or -infinity
* EXCEPTION_UNDERFLOW:: 0
* EXCEPTION_OVERFLOW:: +infinity or -infinity
* EXCEPTION_ZERODIVIDE:: +infinity or -infinity
*
* One value of the mode parameter controls the rounding of numeric values:
* BigDecimal::ROUND_MODE. The values it can take are:
*
* ROUND_UP, :up:: round away from zero
* ROUND_DOWN, :down, :truncate:: round towards zero (truncate)
* ROUND_HALF_UP, :half_up, :default:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round away from zero. (default)
* ROUND_HALF_DOWN, :half_down:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards zero.
* ROUND_HALF_EVEN, :half_even, :banker:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards the even neighbor (Banker's rounding)
* ROUND_CEILING, :ceiling, :ceil:: round towards positive infinity (ceil)
* ROUND_FLOOR, :floor:: round towards negative infinity (floor)
*
*/
static VALUE
BigDecimal_mode(int argc, VALUE *argv, VALUE self)
{
@ -357,17 +405,14 @@ BigDecimal_mode(int argc, VALUE *argv, VALUE self)
fo = VpGetException();
return INT2FIX(fo);
}
if(VP_ROUND_MODE==f) {
/* Rounding mode setting */
fo = VpGetRoundMode();
if(val==Qnil) return INT2FIX(fo);
Check_Type(val, T_FIXNUM);
if(!VpIsRoundMode((unsigned short)FIX2INT(val))) {
rb_raise(rb_eTypeError, "invalid rounding mode");
return Qnil;
}
fo = VpSetRoundMode((unsigned short)FIX2INT(val));
return INT2FIX(fo);
if (VP_ROUND_MODE == f) {
/* Rounding mode setting */
unsigned short sw;
fo = VpGetRoundMode();
if (NIL_P(val)) return INT2FIX(fo);
sw = check_rounding_mode(val);
fo = VpSetRoundMode(sw);
return INT2FIX(fo);
}
rb_raise(rb_eTypeError, "first argument for BigDecimal#mode invalid");
return Qnil;
@ -1252,7 +1297,7 @@ BigDecimal_fix(VALUE self)
}
/* call-seq:
* round(n,mode)
* round(n, mode)
*
* Round to the nearest 1 (by default), returning the result as a BigDecimal.
*
@ -1285,8 +1330,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self)
unsigned short sw = VpGetRoundMode();
int na = rb_scan_args(argc,argv,"02",&vLoc,&vRound);
switch(na) {
switch (rb_scan_args(argc, argv, "02", &vLoc, &vRound)) {
case 0:
iLoc = 0;
break;
@ -1295,15 +1339,10 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self)
iLoc = FIX2INT(vLoc);
break;
case 2:
Check_Type(vLoc, T_FIXNUM);
iLoc = FIX2INT(vLoc);
Check_Type(vRound, T_FIXNUM);
sw = (unsigned short)FIX2INT(vRound);
if(!VpIsRoundMode(sw)) {
rb_raise(rb_eTypeError, "invalid rounding mode");
return Qnil;
}
break;
Check_Type(vLoc, T_FIXNUM);
iLoc = FIX2INT(vLoc);
sw = check_rounding_mode(vRound);
break;
}
pl = VpSetPrecLimit(0);
@ -2140,6 +2179,18 @@ Init_bigdecimal(void)
id_BigDecimal_exception_mode = rb_intern_const("BigDecimal.exception_mode");
id_BigDecimal_rounding_mode = rb_intern_const("BigDecimal.rounding_mode");
id_BigDecimal_precision_limit = rb_intern_const("BigDecimal.precision_limit");
id_up = rb_intern_const("up");
id_down = rb_intern_const("down");
id_truncate = rb_intern_const("truncate");
id_half_up = rb_intern_const("half_up");
id_default = rb_intern_const("default");
id_half_down = rb_intern_const("half_down");
id_half_even = rb_intern_const("half_even");
id_banker = rb_intern_const("banker");
id_ceiling = rb_intern_const("ceiling");
id_ceil = rb_intern_const("ceil");
id_floor = rb_intern_const("floor");
}
/*

View file

@ -5,6 +5,20 @@ require 'thread'
class TestBigDecimal < Test::Unit::TestCase
include TestBigDecimalBase
ROUNDING_MODE_MAP = [
[ BigDecimal::ROUND_UP, :up],
[ BigDecimal::ROUND_DOWN, :down],
[ BigDecimal::ROUND_DOWN, :truncate],
[ BigDecimal::ROUND_HALF_UP, :half_up],
[ BigDecimal::ROUND_HALF_UP, :default],
[ BigDecimal::ROUND_HALF_DOWN, :half_down],
[ BigDecimal::ROUND_HALF_EVEN, :half_even],
[ BigDecimal::ROUND_HALF_EVEN, :banker],
[ BigDecimal::ROUND_CEILING, :ceiling],
[ BigDecimal::ROUND_CEILING, :ceil],
[ BigDecimal::ROUND_FLOOR, :floor],
]
def test_version
assert_equal("1.0.1", BigDecimal.ver)
end
@ -60,6 +74,13 @@ class TestBigDecimal < Test::Unit::TestCase
ensure
BigDecimal.mode(BigDecimal::ROUND_MODE, saved_mode)
end
BigDecimal.save_rounding_mode do
ROUNDING_MODE_MAP.each do |const, sym|
BigDecimal.mode(BigDecimal::ROUND_MODE, sym)
assert_equal(const, BigDecimal.mode(BigDecimal::ROUND_MODE))
end
end
end
def test_thread_local_mode
@ -613,16 +634,21 @@ class TestBigDecimal < Test::Unit::TestCase
assert_equal(2, x.round(0, BigDecimal::ROUND_FLOOR))
assert_raise(TypeError) { x.round(0, 256) }
ROUNDING_MODE_MAP.each do |const, sym|
assert_equal(x.round(0, const), x.round(0, sym))
end
bug3803 = '[ruby-core:32136]'
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))
assert_equal(10**(n+2), x.round(-(n+2), BigDecimal::ROUND_HALF_DOWN), bug3803)
assert_equal(10**(n+2), x.round(-(n+2), BigDecimal::ROUND_HALF_EVEN), bug3803)
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))
assert_equal(1, x.round(0, BigDecimal::ROUND_HALF_DOWN), bug3803)
assert_equal(1, x.round(0, BigDecimal::ROUND_HALF_EVEN), bug3803)
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))
assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_DOWN), bug3803)
assert_equal(-1, x.round(0, BigDecimal::ROUND_HALF_EVEN), bug3803)
end
end