From 6752bd96463557f4ed659e90b1976ceb30058826 Mon Sep 17 00:00:00 2001 From: mrkn Date: Thu, 15 Mar 2018 07:19:45 +0000 Subject: 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 | 217 +++++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 136 insertions(+), 81 deletions(-) (limited to 'object.c') diff --git a/object.c b/object.c index 7a730ef132..62110f7ccf 100644 --- a/object.c +++ b/object.c @@ -3228,19 +3228,8 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj) return rb_convert_to_integer(arg, base, opts_exception_p(opts)); } -/*! - * Parses a string representation of a floating point number. - * - * \param[in] p a string representation of a floating number - * \param[in] badcheck raises an exception on parse error if \a badcheck is non-zero. - * \return the floating point number in the string on success, - * 0.0 on parse error and \a badcheck is zero. - * \note it always fails to parse a hexadecimal representation like "0xAB.CDp+1" when - * \a badcheck is zero, even though it would success if \a badcheck was non-zero. - * This inconsistency is coming from a historical compatibility reason. [ruby-dev:40822] - */ -double -rb_cstr_to_dbl(const char *p, int badcheck) +static double +rb_cstr_to_dbl_raise(const char *p, int badcheck, int raise, int *error) { const char *q; char *end; @@ -3249,71 +3238,76 @@ rb_cstr_to_dbl(const char *p, int badcheck) int w; enum {max_width = 20}; #define OutOfRange() ((end - p > max_width) ? \ - (w = max_width, ellipsis = "...") : \ - (w = (int)(end - p), ellipsis = "")) + (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; + return 0.0; } d = strtod(p, &end); if (errno == ERANGE) { - OutOfRange(); - rb_warning("Float %.*s%s out of range", w, p, ellipsis); - errno = 0; + 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 (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; - } + 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); + errno = 0; + OutOfRange(); + rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis); } return d; } @@ -3321,7 +3315,7 @@ rb_cstr_to_dbl(const char *p, int badcheck) /*! * Parses a string representation of a floating point number. * - * \param[in] str a \c String object representation of a floating number + * \param[in] p a string representation of a floating number * \param[in] badcheck raises an exception on parse error if \a badcheck is non-zero. * \return the floating point number in the string on success, * 0.0 on parse error and \a badcheck is zero. @@ -3330,7 +3324,13 @@ rb_cstr_to_dbl(const char *p, int badcheck) * This inconsistency is coming from a historical compatibility reason. [ruby-dev:40822] */ double -rb_str_to_dbl(VALUE str, int badcheck) +rb_cstr_to_dbl(const char *p, int badcheck) +{ + 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; @@ -3342,7 +3342,12 @@ rb_str_to_dbl(VALUE str, int badcheck) len = RSTRING_LEN(str); if (s) { if (badcheck && memchr(s, '\0', len)) { - rb_raise(rb_eArgError, "string for Float contains null byte"); + 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); @@ -3351,12 +3356,31 @@ rb_str_to_dbl(VALUE str, int badcheck) s = p; } } - ret = rb_cstr_to_dbl(s, badcheck); + 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. + * + * \param[in] str a \c String object representation of a floating number + * \param[in] badcheck raises an exception on parse error if \a badcheck is non-zero. + * \return the floating point number in the string on success, + * 0.0 on parse error and \a badcheck is zero. + * \note it always fails to parse a hexadecimal representation like "0xAB.CDp+1" when + * \a badcheck is zero, even though it would success if \a badcheck was non-zero. + * This inconsistency is coming from a historical compatibility reason. [ruby-dev:40822] + */ +double +rb_str_to_dbl(VALUE str, int badcheck) +{ + return rb_str_to_dbl_raise(str, badcheck, TRUE, NULL); +} + /*! \cond INTERNAL_MACRO */ #define fix2dbl_without_to_f(x) (double)FIX2LONG(x) #define big2dbl_without_to_f(x) rb_big2dbl(x) @@ -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); -- cgit v1.2.3