From 96760236079b15674a6322a2ca41b1528b51afbd Mon Sep 17 00:00:00 2001 From: nobu Date: Wed, 26 Aug 2009 10:20:30 +0000 Subject: * random.c (random_rand): unified random_int and random_float. [ruby-dev:39158]. and fixes [ruby-core:24655], [ruby-core:24677], [ruby-core:24679]. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@24670 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 6 ++ include/ruby/intern.h | 1 + object.c | 10 +++ random.c | 228 +++++++++++++++++++++++++++++++++---------------- test/ruby/test_rand.rb | 94 ++++++++++---------- 5 files changed, 223 insertions(+), 116 deletions(-) diff --git a/ChangeLog b/ChangeLog index c1302361a3..de7888c6fe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Wed Aug 26 19:20:26 2009 Nobuyoshi Nakada + + * random.c (random_rand): unified random_int and random_float. + [ruby-dev:39158]. and fixes [ruby-core:24655], [ruby-core:24677], + [ruby-core:24679]. + Wed Aug 26 18:59:59 2009 Nobuyoshi Nakada * test/test_tempfile.rb: merged from Hongli Lai's fork. diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 93a35563d4..46d3aeae8a 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -466,6 +466,7 @@ VALUE rb_class_inherited_p(VALUE, VALUE); VALUE rb_convert_type(VALUE,int,const char*,const char*); VALUE rb_check_convert_type(VALUE,int,const char*,const char*); VALUE rb_check_to_integer(VALUE, const char *); +VALUE rb_check_to_float(VALUE); VALUE rb_to_int(VALUE); VALUE rb_Integer(VALUE); VALUE rb_to_float(VALUE); diff --git a/object.c b/object.c index b8a33f4697..c2b10832de 100644 --- a/object.c +++ b/object.c @@ -2299,6 +2299,16 @@ rb_to_float(VALUE val) return rb_convert_type(val, T_FLOAT, "Float", "to_f"); } +VALUE +rb_check_to_float(VALUE val) +{ + if (TYPE(val) == T_FLOAT) return val; + if (!rb_obj_is_kind_of(val, rb_cNumeric)) { + return Qnil; + } + return rb_check_convert_type(val, T_FLOAT, "Float", "to_f"); +} + double rb_num2dbl(VALUE val) { diff --git a/random.c b/random.c index a36fb3ac3d..3b5729f965 100644 --- a/random.c +++ b/random.c @@ -178,6 +178,16 @@ genrand_real(struct MT *mt) unsigned int a = genrand_int32(mt)>>5, b = genrand_int32(mt)>>6; return(a*67108864.0+b)*(1.0/9007199254740992.0); } + +/* generates a random number on [0,1] with 53-bit resolution*/ +static double int_pair_to_real_inclusive(unsigned int a, unsigned int b); +static double +genrand_real2(struct MT *mt) +{ + unsigned int a = genrand_int32(mt), b = genrand_int32(mt); + return int_pair_to_real_inclusive(a, b); +} + /* These real versions are due to Isaku Wada, 2002/01/09 added */ #undef N @@ -238,6 +248,57 @@ rb_genrand_real(void) #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) #define SIZEOF_INT32 (31/CHAR_BIT + 1) +static double +int_pair_to_real_inclusive(unsigned int a, unsigned int b) +{ + VALUE x = rb_big_new(roomof(64, BITSPERDIG), 1); + VALUE m = rb_big_new(roomof(53, BITSPERDIG), 1); + BDIGIT *xd = BDIGITS(x); + int i = 0; + double r; + + xd[i++] = (BDIGIT)b; +#if BITSPERDIG < 32 + xd[i++] = (BDIGIT)(b >> BITSPERDIG); +#endif + xd[i++] = (BDIGIT)a; +#if BITSPERDIG < 32 + xd[i++] = (BDIGIT)(a >> BITSPERDIG); +#endif + xd = BDIGITS(m); +#if BITSPERDIG < 53 + MEMZERO(xd, BDIGIT, roomof(53, BITSPERDIG) - 1); +#endif + xd[53 / BITSPERDIG] = 1 << 53 % BITSPERDIG; + xd[0] |= 1; + x = rb_big_mul(x, m); + if (FIXNUM_P(x)) { +#if CHAR_BIT * SIZEOF_LONG > 64 + r = (double)(FIX2ULONG(x) >> 64); +#else + return 0.0; +#endif + } + else { +#if 64 % BITSPERDIG == 0 + long len = RBIGNUM_LEN(x); + xd = BDIGITS(x); + MEMMOVE(xd, xd + 64 / BITSPERDIG, BDIGIT, len - 64 / BITSPERDIG); + MEMZERO(xd + len - 64 / BITSPERDIG, BDIGIT, 64 / BITSPERDIG); + r = rb_big2dbl(x); +#else + x = rb_big_rshift(x, INT2FIX(64)); + if (FIXNUM_P(x)) { + r = (double)FIX2ULONG(x); + } + else { + r = rb_big2dbl(x); + } +#endif + } + return ldexp(r, -53); +} + VALUE rb_cRandom; #define id_minus '-' #define id_plus '+' @@ -829,19 +890,14 @@ rb_random_bytes(VALUE obj, long n) } static VALUE -range_values(VALUE vmax, VALUE *begp) +range_values(VALUE vmax, VALUE *begp, int *exclp) { - VALUE end, r, one = INT2FIX(1); - int excl; + VALUE end, r; - if (!rb_range_values(vmax, begp, &end, &excl)) return Qfalse; + if (!rb_range_values(vmax, begp, &end, exclp)) return Qfalse; if (!rb_respond_to(end, id_minus)) return Qfalse; r = rb_funcall2(end, id_minus, 1, begp); if (NIL_P(r)) return Qfalse; - if (!excl && rb_respond_to(r, id_plus)) { - r = rb_funcall2(r, id_plus, 1, &one); - if (NIL_P(r)) return Qfalse; - } return r; } @@ -853,7 +909,7 @@ add_to_begin(VALUE beg, VALUE offset) } static VALUE -rand_int(struct MT *mt, VALUE vmax) +rand_int(struct MT *mt, VALUE vmax, int restrictive) { long max; unsigned long r; @@ -861,13 +917,18 @@ rand_int(struct MT *mt, VALUE vmax) if (FIXNUM_P(vmax)) { max = FIX2LONG(vmax); if (!max) return Qnil; - r = limited_rand(mt, (unsigned long)(max < 0 ? -max : max) - 1); + if (max < 0) { + if (restrictive) return Qnil; + max = -max; + } + r = limited_rand(mt, (unsigned long)max - 1); return ULONG2NUM(r); } else { VALUE ret; if (rb_bigzero_p(vmax)) return Qnil; if (!RBIGNUM_SIGN(vmax)) { + if (restrictive) return Qnil; vmax = rb_big_clone(vmax); RBIGNUM_SET_SIGN(vmax, 1); } @@ -884,86 +945,110 @@ rand_int(struct MT *mt, VALUE vmax) } } +static inline double +float_value(VALUE v) +{ + double x = RFLOAT_VALUE(v); + if (isinf(x) || isnan(x)) { + VALUE error = INT2FIX(EDOM); + rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError)); + } + return x; +} + /* - * call-seq: prng.int(limit) -> integer + * call-seq: + * prng.rand -> float + * prng.rand(limit) -> number * * When the argument is an +Integer+ or a +Bignum+, it returns a * random integer greater than or equal to zero and less than the * argument. Unlike Random#rand, when the argument is a negative * integer or zero, it raises an ArgumentError. * + * When the argument is a +Float+, it returns a random floating point + * number between 0.0 and _max_, including 0.0 and excluding _max_. + * * When the argument _limit_ is a +Range+, it returns a random - * integer from integers where range.member?(integer) == true. - * prng.int(5..9) # => one of [5, 6, 7, 8, 9] - * prng.int(5...9) # => one of [5, 6, 7, 8] + * number where range.member?(number) == true. + * prng.rand(5..9) # => one of [5, 6, 7, 8, 9] + * prng.rand(5...9) # => one of [5, 6, 7, 8] + * prng.rand(5.0..9.0) # => between 5.0 and 9.0, including 9.0 + * prng.rand(5.0...9.0) # => between 5.0 and 9.0, excluding 9.0 * * +begin+/+end+ of the range have to have subtruct and add methods. * * Otherwise, it raises an ArgumentError. */ -VALUE -rb_random_int(VALUE obj, VALUE vmax) -{ - VALUE v, beg = Qundef; - rb_random_t *rnd = get_rnd(obj); - - v = rb_check_to_integer(vmax, "to_int"); - if (NIL_P(v)) { - /* range like object support */ - if (!(v = range_values(vmax, &beg))) { - v = vmax; - } - v = rb_to_int(v); - beg = rb_to_int(beg); - } - v = rand_int(&rnd->mt, v); - if (NIL_P(v)) v = INT2FIX(0); - return add_to_begin(beg, v); -} - -#define random_int rb_random_int - -/* - * call-seq: - * prng.float -> float - * prng.float([max=1.0]) -> float - * - * Returns a random floating point number between 0.0 and _max_, - * including 0.0 and excluding _max_. - */ static VALUE -random_float(int argc, VALUE *argv, VALUE obj) +random_rand(int argc, VALUE *argv, VALUE obj) { rb_random_t *rnd = get_rnd(obj); - VALUE vmax, beg = Qundef; - double max = 0, r; + VALUE vmax, beg = Qundef, v; + int excl = 0; - switch (argc) { - case 0: - break; - case 1: - vmax = argv[0]; - if (TYPE(vmax) == T_FLOAT || - !NIL_P(vmax = rb_to_float(vmax)) || - (vmax = range_values(vmax, &beg)) != Qfalse) { - max = RFLOAT_VALUE(vmax); - if (isinf(max) || isnan(max)) { - VALUE error = INT2FIX(EDOM); - rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError)); + if (argc == 0) { + zero_arg: + return rb_float_new(genrand_real(&rnd->mt)); + } + else if (argc != 1) { + rb_raise(rb_eArgError, "wrong number of arguments (%d for 0..1)", argc); + } + vmax = argv[0]; + if (NIL_P(vmax)) { + goto zero_arg; + } + else if (TYPE(vmax) != T_FLOAT && (v = rb_check_to_integer(vmax, "to_int"), !NIL_P(v))) { + v = rand_int(&rnd->mt, vmax = v, 1); + } + else if (v = rb_check_to_float(vmax), !NIL_P(v)) { + double max = float_value(v); + if (max > 0.0) + v = rb_float_new(max * genrand_real(&rnd->mt)); + else + v = Qnil; + } + else if ((v = range_values(vmax, &beg, &excl)) != Qfalse) { + vmax = v; + if (TYPE(vmax) != T_FLOAT && (v = rb_check_to_integer(vmax, "to_int"), !NIL_P(v))) { + long max; + vmax = v; + v = Qnil; + if (FIXNUM_P(vmax)) { + fixnum: + if ((max = FIX2LONG(vmax) - excl) > 0) { + unsigned long r = limited_rand(&rnd->mt, (unsigned long)max); + v = ULONG2NUM(r); + } + } + else if (BUILTIN_TYPE(vmax) == T_BIGNUM && RBIGNUM_SIGN(vmax) && !rb_bigzero_p(vmax)) { + vmax = excl ? rb_big_minus(vmax, INT2FIX(1)) : rb_big_norm(vmax); + if (FIXNUM_P(vmax)) { + excl = 0; + goto fixnum; + } + v = limited_big_rand(&rnd->mt, RBIGNUM(vmax)); } } - else { - beg = Qundef; - Check_Type(argv[0], T_FLOAT); + else if (v = rb_check_to_float(vmax), !NIL_P(v)) { + double max = float_value(v), r; + v = Qnil; + if (max > 0.0) { + if (excl) { + r = genrand_real(&rnd->mt); + } + else { + r = genrand_real2(&rnd->mt); + } + v = rb_float_new(r * max); + } } - break; - default: - rb_scan_args(argc, argv, "01", 0); - break; } - r = genrand_real(&rnd->mt); - if (argc) r *= max; - return add_to_begin(beg, rb_float_new(r)); + else { + NUM2LONG(vmax); + } + if (NIL_P(v)) rb_raise(rb_eArgError, "invalid argument"); + return add_to_begin(beg, v); } /* @@ -1019,7 +1104,7 @@ rb_f_rand(int argc, VALUE *argv, VALUE obj) rb_scan_args(argc, argv, "01", &vmax); if (NIL_P(vmax)) goto zero_arg; vmax = rb_to_int(vmax); - if (vmax == INT2FIX(0) || NIL_P(r = rand_int(mt, vmax))) { + if (vmax == INT2FIX(0) || NIL_P(r = rand_int(mt, vmax, 0))) { zero_arg: return DBL2NUM(genrand_real(mt)); } @@ -1058,9 +1143,8 @@ Init_Random(void) rb_cRandom = rb_define_class("Random", rb_cObject); rb_define_alloc_func(rb_cRandom, random_alloc); rb_define_method(rb_cRandom, "initialize", random_init, -1); - rb_define_method(rb_cRandom, "int", random_int, 1); + rb_define_method(rb_cRandom, "rand", random_rand, -1); rb_define_method(rb_cRandom, "bytes", random_bytes, 1); - rb_define_method(rb_cRandom, "float", random_float, -1); rb_define_method(rb_cRandom, "seed", random_get_seed, 0); rb_define_method(rb_cRandom, "initialize_copy", random_copy, 1); rb_define_method(rb_cRandom, "marshal_dump", random_dump, 0); diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index 39444af8ac..446211587f 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -10,14 +10,14 @@ class TestRand < Test::Unit::TestCase w = w.to_i assert_equal(w, rand(m)) rnds.each do |rnd| - assert_equal(w, rnd.int(m)) + assert_equal(w, rnd.rand(m)) end rnds2.each do |rnd| - r=rnd.int(i...(m+i)) + r=rnd.rand(i...(m+i)) assert_equal(w+i, r) end rnds3.each do |rnd| - r=rnd.int(i..(m+i-1)) + r=rnd.rand(i..(m+i-1)) assert_equal(w+i, r) end rnds << Marshal.load(Marshal.dump(rnds[-1])) @@ -110,64 +110,61 @@ class TestRand < Test::Unit::TestCase 0x4000000000000001) end - def test_neg_0x10000000000 + def test_0x10000000000 ws = %w(455570294424 1073054410371 790795084744 2445173525 1088503892627) assert_random_int(ws, 0x10000000000, 3) - assert_random_int(ws, -0x10000000000, 3) end - def test_neg_0x10000 + def test_0x10000 ws = %w(2732 43567 42613 52416 45891) assert_random_int(ws, 0x10000) - assert_random_int(ws, -0x10000) end def test_types srand(0) rnd = Random.new(0) assert_equal(44, rand(100.0)) - assert_equal(44, rnd.int(100.0)) + assert_equal(44, rnd.rand(100)) assert_equal(1245085576965981900420779258691, rand((2**100).to_f)) - assert_equal(1245085576965981900420779258691, rnd.int((2**100).to_f)) + assert_equal(1245085576965981900420779258691, rnd.rand(2**100)) assert_equal(914679880601515615685077935113, rand(-(2**100).to_f)) - assert_equal(914679880601515615685077935113, rnd.int(-(2**100).to_f)) srand(0) rnd = Random.new(0) assert_equal(997707939797331598305742933184, rand(2**100)) - assert_equal(997707939797331598305742933184, rnd.int(2**100)) + assert_equal(997707939797331598305742933184, rnd.rand(2**100)) assert_in_delta(0.602763376071644, rand((2**100).coerce(0).first), 0.000000000000001) - assert_equal(0, rnd.int((2**100).coerce(0).first)) + assert_raise(ArgumentError) {rnd.rand((2**100).coerce(0).first)} srand(0) rnd = Random.new(0) assert_in_delta(0.548813503927325, rand(nil), 0.000000000000001) - assert_in_delta(0.548813503927325, rnd.float(), + assert_in_delta(0.548813503927325, rnd.rand(), 0.000000000000001) srand(0) rnd = Random.new(0) o = Object.new def o.to_int; 100; end assert_equal(44, rand(o)) - assert_equal(44, rnd.int(o)) + assert_equal(44, rnd.rand(o)) assert_equal(47, rand(o)) - assert_equal(47, rnd.int(o)) + assert_equal(47, rnd.rand(o)) assert_equal(64, rand(o)) - assert_equal(64, rnd.int(o)) + assert_equal(64, rnd.rand(o)) end def test_srand srand assert_kind_of(Integer, rand(2)) - assert_kind_of(Integer, Random.new.int(2)) + assert_kind_of(Integer, Random.new.rand(2)) srand(2**100) rnd = Random.new(2**100) %w(3258412053).each {|w| assert_equal(w.to_i, rand(0x100000000)) - assert_equal(w.to_i, rnd.int(0x100000000)) + assert_equal(w.to_i, rnd.rand(0x100000000)) } end @@ -183,21 +180,25 @@ class TestRand < Test::Unit::TestCase def test_random_gc r = Random.new(0) %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r.int(0x100000000)) + assert_equal(w.to_i, r.rand(0x100000000)) end GC.start %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r.int(0x100000000)) + assert_equal(w.to_i, r.rand(0x100000000)) end end def test_random_type_error assert_raise(TypeError) { Random.new(Object.new) } - assert_raise(TypeError) { Random.new(0).int(Object.new) } + assert_raise(TypeError) { Random.new(0).rand(Object.new) } end def test_random_argument_error - assert_raise(ArgumentError) { Random.new(0).float(0, 0) } + r = Random.new(0) + assert_raise(ArgumentError) { r.rand(0, 0) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1.0) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(0) } end def test_random_seed @@ -210,17 +211,17 @@ class TestRand < Test::Unit::TestCase r1 = Random.new(0) r2 = r1.dup %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r1.int(0x100000000)) + assert_equal(w.to_i, r1.rand(0x100000000)) end %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r2.int(0x100000000)) + assert_equal(w.to_i, r2.rand(0x100000000)) end r2 = r1.dup %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r1.int(0x100000000)) + assert_equal(w.to_i, r1.rand(0x100000000)) end %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r2.int(0x100000000)) + assert_equal(w.to_i, r2.rand(0x100000000)) end end @@ -309,16 +310,16 @@ END srand(0) assert_equal(state, r.instance_eval { state }) assert_equal(state, Random.instance_eval { state }) - r.int(0x100) + r.rand(0x100) assert_equal(state, r.instance_eval { state }) end def test_random_left r = Random.new(0) assert_equal(1, r.instance_eval { left }) - r.int(0x100) + r.rand(0x100) assert_equal(624, r.instance_eval { left }) - r.int(0x100) + r.rand(0x100) assert_equal(623, r.instance_eval { left }) srand(0) assert_equal(1, Random.instance_eval { left }) @@ -337,29 +338,34 @@ END def test_random_range r = Random.new(0) - %w(9 5 8).each {|w| assert_equal(w.to_i, r.int(5..9)) } - %w(-237 731 383).each {|w| assert_equal(w.to_i, r.int(-1000..1000)) } + %w(9 5 8).each {|w| assert_equal(w.to_i, r.rand(5..9)) } + %w(-237 731 383).each {|w| assert_equal(w.to_i, r.rand(-1000..1000)) } %w(1267650600228229401496703205382 1267650600228229401496703205384 1267650600228229401496703205383).each do |w| - assert_equal(w.to_i, r.int(2**100+5..2**100+9)) + assert_equal(w.to_i, r.rand(2**100+5..2**100+9)) end - assert_equal(3, r.int(3.1..4), '[ruby-core:24679]') + v = r.rand(3.1..4) + assert_instance_of(Float, v, '[ruby-core:24679]') + assert_includes(3.1..4, v) end def test_random_float r = Random.new(0) - assert_in_delta(0.5488135039273248, r.float, 0.0001) - assert_in_delta(0.7151893663724195, r.float, 0.0001) - assert_in_delta(0.6027633760716439, r.float, 0.0001) - assert_in_delta(1.0897663659937937, r.float(2), 0.0001) - assert_in_delta(5.3704626067153264e+29, r.float(2**100), 10**25) + assert_in_delta(0.5488135039273248, r.rand, 0.0001) + assert_in_delta(0.7151893663724195, r.rand, 0.0001) + assert_in_delta(0.6027633760716439, r.rand, 0.0001) + assert_in_delta(1.0897663659937937, r.rand(2.0), 0.0001) + assert_in_delta(5.3704626067153264e+29, r.rand((2**100).to_f), 10**25) - assert_raise(Errno::EDOM, Errno::ERANGE) { r.float(1.0 / 0.0) } - assert_raise(Errno::EDOM, Errno::ERANGE) { r.float(0.0 / 0.0) } + assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) } + assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) } - # is this intentional? - assert_raise(TypeError) { r.float(1..2) } + r = Random.new(0) + assert_in_delta(1.5488135039273248, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') + assert_in_delta(1.7151893663724195, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') + assert_in_delta(7.027633760716439, r.rand(1.0...11.0), 0.0001, '[ruby-core:24655]') + assert_in_delta(3.0897663659937937, r.rand(2.0...4.0), 0.0001, '[ruby-core:24655]') end def test_random_equal @@ -368,9 +374,9 @@ END assert(r == r.dup) r1 = r.dup r2 = r.dup - r1.int(0x100) + r1.rand(0x100) assert(r1 != r2) - r2.int(0x100) + r2.rand(0x100) assert(r1 == r2) end end -- cgit v1.2.3