diff options
-rw-r--r-- | object.c | 217 | ||||
-rw-r--r-- | test/ruby/test_float.rb | 38 |
2 files changed, 174 insertions, 81 deletions
@@ -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); 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") {} |