summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--ext/bigdecimal/bigdecimal.c173
-rw-r--r--test/bigdecimal/test_bigdecimal.rb38
3 files changed, 155 insertions, 67 deletions
diff --git a/ChangeLog b/ChangeLog
index 17ca6e5bd3..c8607786c5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c
index 18638e1fe8..64eebecad1 100644
--- a/ext/bigdecimal/bigdecimal.c
+++ b/ext/bigdecimal/bigdecimal.c
@@ -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");
}
/*
diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb
index 2efa65b57b..351fd2dbab 100644
--- a/test/bigdecimal/test_bigdecimal.rb
+++ b/test/bigdecimal/test_bigdecimal.rb
@@ -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