From: redmine@... Date: 2011-04-14T12:30:23+09:00 Subject: [ruby-core:35748] [Ruby 1.9 - Feature #4574] Numeric#within Issue #4574 has been updated by Motohiro KOSAKI. More off topic. Here is very similar discussion by phthonia. http://stackoverflow.com/questions/4092528/how-to-clamp-an-integer-to-some-range-in-python ---------------------------------------- Feature #4574: Numeric#within http://redmine.ruby-lang.org/issues/4574 Author: Yusuke Endoh Status: Open Priority: Normal Assignee: Yukihiro Matsumoto Category: Target version: 1.9.3 Hello, Many people have written programs that limits an integer/float within a range like: v = [[v, min].max, max].min or v = v < min ? min : (v < max ? v : max) or if v < min v = min elsif max < v v = max end . But these are all too complex in spite of frequent idiom. "[min, v, max].sort[1]" is cool, but slightly cryptic. So I propose Numeric#within. v = v.within(min, max) Examples: p 0.within(2, 5) #=> 2 p 2.within(2, 5) #=> 2 p 3.within(2, 5) #=> 3 p 5.within(2, 5) #=> 5 p 6.within(2, 5) #=> 6 What do you think? Some Japanese committers agree with this idea, and proposed other candidates of method name: - Numeric#bound - Numeric#limit - Numeric#clip - Numeric#into - Numeric#crop - Numeric#trim - Range#bound # usage: (2..5).bound(0) Anyway, I think that the length should be less than 9, because it should be shorter than the sort idiom: [min, v, max].sort[1] v.xxxxxxxxx(min, max) Here is a patch including both Numeric#within and Range#bound. diff --git a/numeric.c b/numeric.c index 34c378b..dad485f 100644 --- a/numeric.c +++ b/numeric.c @@ -1600,6 +1600,43 @@ num_round(int argc, VALUE* argv, VALUE num) return flo_round(argc, argv, rb_Float(num)); } +static int +r_le(VALUE a, VALUE b) +{ + int c; + VALUE r = rb_funcall(a, rb_intern("<=>"), 1, b); + + if (NIL_P(r)) + return (int)Qfalse; + c = rb_cmpint(r, a, b); + if (c == 0) + return (int)INT2FIX(0); + if (c < 0) + return (int)Qtrue; + return (int)Qfalse; +} + +/* + * call-seq: + * num.within(min, max) -> new_num + * + * Bounds num so that min <= new_num <= max. + * Returns min when num < min, max when num > end, otherwise + * num itself. + */ + +static VALUE +num_within(VALUE num, VALUE min, VALUE max) +{ + if (r_le(min, num)) { + if (r_le(max, num)) { + return max; + } + return num; + } + return min; +} + /* * call-seq: * num.truncate -> integer @@ -3424,6 +3461,7 @@ Init_Numeric(void) rb_define_method(rb_cNumeric, "round", num_round, -1); rb_define_method(rb_cNumeric, "truncate", num_truncate, 0); rb_define_method(rb_cNumeric, "step", num_step, -1); + rb_define_method(rb_cNumeric, "within", num_within, 1); rb_cInteger = rb_define_class("Integer", rb_cNumeric); rb_undef_alloc_func(rb_cInteger); diff --git a/range.c b/range.c index 1866df1..2dd99f5 100644 --- a/range.c +++ b/range.c @@ -923,6 +923,41 @@ range_cover(VALUE range, VALUE val) return Qfalse; } +/* + * call-seq: + * rng.bound(val) -> new_val + * + * Bounds val so that beg <= new_val <= end. This method returns + * beg when val < ben, end when val > end, otherwise, val itself. + * Raises an exception if val >= end and the range is exclusive. + * + * (2..5).bound(0) #=> 2 + * (2..5).bound(2) #=> 2 + * (2..5).bound(3) #=> 3 + * (2..5).bound(5) #=> 5 + * (2..5).bound(6) #=> 5 + * (2...5).bound(6) #=> ArgumentError + */ + +static VALUE +range_bound(VALUE range, VALUE val) +{ + VALUE beg, end; + + beg = RANGE_BEG(range); + end = RANGE_END(range); + if (r_le(beg, val)) { + if (r_le(end, val)) { + if (EXCL(range)) { + rb_raise(rb_eArgError, "more than or equal to the excluded range"); + } + return end; + } + return val; + } + return beg; +} + static VALUE range_dumper(VALUE range) { @@ -1051,4 +1086,5 @@ Init_Range(void) rb_define_method(rb_cRange, "member?", range_include, 1); rb_define_method(rb_cRange, "include?", range_include, 1); rb_define_method(rb_cRange, "cover?", range_cover, 1); + rb_define_method(rb_cRange, "bound", range_bound, 1); } -- Yusuke Endoh -- http://redmine.ruby-lang.org