From 2993b4423d0648af7652316505a23a074072a517 Mon Sep 17 00:00:00 2001 From: mrkn Date: Thu, 15 Mar 2018 07:19:45 +0000 Subject: [PATCH] Add `exception:` keyword in Kernel#Float() Support `exception:` keyword argument in `Kernel#Float()`. If `exception:` is `false`, `Kernel#Float()` returns `nil` if the given value cannot be interpreted as a float value. The default value of `exception:` is `true`. This is part of [Feature #12732]. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62758 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- object.c | 279 ++++++++++++++++++++++++---------------- test/ruby/test_float.rb | 38 ++++++ 2 files changed, 205 insertions(+), 112 deletions(-) diff --git a/object.c b/object.c index 7a730ef132..62110f7ccf 100644 --- a/object.c +++ b/object.c @@ -3228,6 +3228,90 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj) return rb_convert_to_integer(arg, base, opts_exception_p(opts)); } +static double +rb_cstr_to_dbl_raise(const char *p, int badcheck, int raise, int *error) +{ + const char *q; + char *end; + double d; + const char *ellipsis = ""; + int w; + enum {max_width = 20}; +#define OutOfRange() ((end - p > max_width) ? \ + (w = max_width, ellipsis = "...") : \ + (w = (int)(end - p), ellipsis = "")) + + if (!p) return 0.0; + q = p; + while (ISSPACE(*p)) p++; + + if (!badcheck && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + return 0.0; + } + + d = strtod(p, &end); + if (errno == ERANGE) { + OutOfRange(); + rb_warning("Float %.*s%s out of range", w, p, ellipsis); + errno = 0; + } + if (p == end) { + if (badcheck) { + bad: + if (raise) + rb_invalid_str(q, "Float()"); + else { + if (error) *error = 1; + return 0.0; + } + } + return d; + } + if (*end) { + char buf[DBL_DIG * 4 + 10]; + char *n = buf; + char *e = buf + sizeof(buf) - 1; + char prev = 0; + + while (p < end && n < e) prev = *n++ = *p++; + while (*p) { + if (*p == '_') { + /* remove an underscore between digits */ + if (n == buf || !ISDIGIT(prev) || (++p, !ISDIGIT(*p))) { + if (badcheck) goto bad; + break; + } + } + prev = *p++; + if (n < e) *n++ = prev; + } + *n = '\0'; + p = buf; + + if (!badcheck && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + return 0.0; + } + + d = strtod(p, &end); + if (errno == ERANGE) { + OutOfRange(); + rb_warning("Float %.*s%s out of range", w, p, ellipsis); + errno = 0; + } + if (badcheck) { + if (!end || p == end) goto bad; + while (*end && ISSPACE(*end)) end++; + if (*end) goto bad; + } + } + if (errno == ERANGE) { + errno = 0; + OutOfRange(); + rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis); + } + return d; +} + /*! * Parses a string representation of a floating point number. * @@ -3242,82 +3326,44 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj) double rb_cstr_to_dbl(const char *p, int badcheck) { - const char *q; - char *end; - double d; - const char *ellipsis = ""; - int w; - enum {max_width = 20}; -#define OutOfRange() ((end - p > max_width) ? \ - (w = max_width, ellipsis = "...") : \ - (w = (int)(end - p), ellipsis = "")) - - if (!p) return 0.0; - q = p; - while (ISSPACE(*p)) p++; - - if (!badcheck && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - return 0.0; - } - - d = strtod(p, &end); - if (errno == ERANGE) { - OutOfRange(); - rb_warning("Float %.*s%s out of range", w, p, ellipsis); - errno = 0; - } - if (p == end) { - if (badcheck) { - bad: - rb_invalid_str(q, "Float()"); - } - return d; - } - if (*end) { - char buf[DBL_DIG * 4 + 10]; - char *n = buf; - char *e = buf + sizeof(buf) - 1; - char prev = 0; - - while (p < end && n < e) prev = *n++ = *p++; - while (*p) { - if (*p == '_') { - /* remove an underscore between digits */ - if (n == buf || !ISDIGIT(prev) || (++p, !ISDIGIT(*p))) { - if (badcheck) goto bad; - break; - } - } - prev = *p++; - if (n < e) *n++ = prev; - } - *n = '\0'; - p = buf; - - if (!badcheck && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - return 0.0; - } - - d = strtod(p, &end); - if (errno == ERANGE) { - OutOfRange(); - rb_warning("Float %.*s%s out of range", w, p, ellipsis); - errno = 0; - } - if (badcheck) { - if (!end || p == end) goto bad; - while (*end && ISSPACE(*end)) end++; - if (*end) goto bad; - } - } - if (errno == ERANGE) { - errno = 0; - OutOfRange(); - rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis); - } - return d; + return rb_cstr_to_dbl_raise(p, badcheck, TRUE, NULL); } +static double +rb_str_to_dbl_raise(VALUE str, int badcheck, int raise, int *error) +{ + char *s; + long len; + double ret; + VALUE v = 0; + + StringValue(str); + s = RSTRING_PTR(str); + len = RSTRING_LEN(str); + if (s) { + if (badcheck && memchr(s, '\0', len)) { + if (raise) + rb_raise(rb_eArgError, "string for Float contains null byte"); + else { + if (error) *error = 1; + return 0.0; + } + } + if (s[len]) { /* no sentinel somehow */ + char *p = ALLOCV(v, (size_t)len + 1); + MEMCPY(p, s, char, len); + p[len] = '\0'; + s = p; + } + } + ret = rb_cstr_to_dbl_raise(s, badcheck, raise, error); + if (v) + ALLOCV_END(v); + return ret; +} + +FUNC_MINIMIZED(double rb_str_to_dbl(VALUE str, int badcheck)); + /*! * Parses a string representation of a floating point number. * @@ -3332,29 +3378,7 @@ rb_cstr_to_dbl(const char *p, int badcheck) double rb_str_to_dbl(VALUE str, int badcheck) { - char *s; - long len; - double ret; - VALUE v = 0; - - StringValue(str); - s = RSTRING_PTR(str); - len = RSTRING_LEN(str); - if (s) { - if (badcheck && memchr(s, '\0', len)) { - rb_raise(rb_eArgError, "string for Float contains null byte"); - } - if (s[len]) { /* no sentinel somehow */ - char *p = ALLOCV(v, (size_t)len + 1); - MEMCPY(p, s, char, len); - p[len] = '\0'; - s = p; - } - } - ret = rb_cstr_to_dbl(s, badcheck); - if (v) - ALLOCV_END(v); - return ret; + return rb_str_to_dbl_raise(str, badcheck, TRUE, NULL); } /*! \cond INTERNAL_MACRO */ @@ -3390,7 +3414,7 @@ implicit_conversion_to_float(VALUE val) } static int -to_float(VALUE *valp) +to_float(VALUE *valp, int raise_exception) { VALUE val = *valp; if (SPECIAL_CONST_P(val)) { @@ -3401,7 +3425,7 @@ to_float(VALUE *valp) else if (FLONUM_P(val)) { return T_FLOAT; } - else { + else if (raise_exception) { conversion_to_float(val); } } @@ -3423,6 +3447,42 @@ to_float(VALUE *valp) return T_NONE; } +static VALUE +convert_type_to_float_protected(VALUE val) +{ + return rb_convert_type_with_id(val, T_FLOAT, "Float", id_to_f); +} + +static VALUE +rb_convert_to_float(VALUE val, int raise_exception) +{ + switch (to_float(&val, raise_exception)) { + case T_FLOAT: + return val; + case T_STRING: + if (!raise_exception) { + int e = 0; + double x = rb_str_to_dbl_raise(val, TRUE, raise_exception, &e); + return e ? Qnil : DBL2NUM(x); + } + return DBL2NUM(rb_str_to_dbl(val, TRUE)); + case T_NONE: + if (SPECIAL_CONST_P(val) && !raise_exception) + return Qnil; + } + + if (!raise_exception) { + int state; + VALUE result = rb_protect(convert_type_to_float_protected, val, &state); + if (state) rb_set_errinfo(Qnil); + return result; + } + + return rb_convert_type_with_id(val, T_FLOAT, "Float", id_to_f); +} + +FUNC_MINIMIZED(VALUE rb_Float(VALUE val)); + /*! * Equivalent to \c Kernel\#Float in Ruby. * @@ -3432,17 +3492,9 @@ to_float(VALUE *valp) VALUE rb_Float(VALUE val) { - switch (to_float(&val)) { - case T_FLOAT: - return val; - case T_STRING: - return DBL2NUM(rb_str_to_dbl(val, TRUE)); - } - return rb_convert_type_with_id(val, T_FLOAT, "Float", id_to_f); + return rb_convert_to_float(val, TRUE); } -static VALUE FUNC_MINIMIZED(rb_f_float(VALUE obj, VALUE arg)); /*!< \private */ - /* * call-seq: * Float(arg) -> float @@ -3459,9 +3511,12 @@ static VALUE FUNC_MINIMIZED(rb_f_float(VALUE obj, VALUE arg)); /*!< \private */ */ static VALUE -rb_f_float(VALUE obj, VALUE arg) +rb_f_float(int argc, VALUE *argv, VALUE obj) { - return rb_Float(arg); + VALUE arg = Qnil, opts = Qnil; + + rb_scan_args(argc, argv, "1:", &arg, &opts); + return rb_convert_to_float(arg, opts_exception_p(opts)); } static VALUE @@ -3482,7 +3537,7 @@ numeric_to_float(VALUE val) VALUE rb_to_float(VALUE val) { - switch (to_float(&val)) { + switch (to_float(&val, TRUE)) { case T_FLOAT: return val; } @@ -4018,7 +4073,7 @@ InitVM_Object(void) rb_define_global_function("format", rb_f_sprintf, -1); /* in sprintf.c */ rb_define_global_function("Integer", rb_f_integer, -1); - rb_define_global_function("Float", rb_f_float, 1); + rb_define_global_function("Float", rb_f_float, -1); rb_define_global_function("String", rb_f_string, 1); rb_define_global_function("Array", rb_f_array, 1); diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index bddd10bf10..24bed9269c 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -783,6 +783,7 @@ class TestFloat < Test::Unit::TestCase assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?) assert_raise(TypeError) { Float(nil) } + assert_raise(TypeError) { Float(:test) } o = Object.new def o.to_f; inf = Float::INFINITY; inf/inf; end assert_predicate(Float(o), :nan?) @@ -793,6 +794,43 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}} end + def test_Float_with_exception_keyword + assert_raise(ArgumentError) { + Float(".", exception: true) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Float(".", exception: false)) + } + assert_raise(RangeError) { + Float(1i, exception: true) + } + assert_nothing_raised(RangeError) { + assert_equal(nil, Float(1i, exception: false)) + } + assert_raise(TypeError) { + Float(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(:test, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + o = Object.new + def o.to_f; 3.14; end + assert_equal(3.14, Float(o, exception: false)) + } + assert_nothing_raised(RuntimeError) { + o = Object.new + def o.to_f; raise; end + assert_equal(nil, Float(o, exception: false)) + } + end + def test_num2dbl assert_raise(ArgumentError, "comparison of String with 0 failed") do 1.0.step(2.0, "0.5") {}