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
This commit is contained in:
mrkn 2018-03-15 07:19:45 +00:00
parent 2cfc5b03da
commit 2993b4423d
2 changed files with 205 additions and 112 deletions

117
object.c
View File

@ -3228,19 +3228,8 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj)
return rb_convert_to_integer(arg, base, opts_exception_p(opts)); return rb_convert_to_integer(arg, base, opts_exception_p(opts));
} }
/*! static double
* Parses a string representation of a floating point number. rb_cstr_to_dbl_raise(const char *p, int badcheck, int raise, int *error)
*
* \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)
{ {
const char *q; const char *q;
char *end; char *end;
@ -3269,7 +3258,12 @@ rb_cstr_to_dbl(const char *p, int badcheck)
if (p == end) { if (p == end) {
if (badcheck) { if (badcheck) {
bad: bad:
if (raise)
rb_invalid_str(q, "Float()"); rb_invalid_str(q, "Float()");
else {
if (error) *error = 1;
return 0.0;
}
} }
return d; return d;
} }
@ -3321,7 +3315,7 @@ rb_cstr_to_dbl(const char *p, int badcheck)
/*! /*!
* Parses a string representation of a floating point number. * 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. * \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, * \return the floating point number in the string on success,
* 0.0 on parse error and \a badcheck is zero. * 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] * This inconsistency is coming from a historical compatibility reason. [ruby-dev:40822]
*/ */
double 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; char *s;
long len; long len;
@ -3342,7 +3342,12 @@ rb_str_to_dbl(VALUE str, int badcheck)
len = RSTRING_LEN(str); len = RSTRING_LEN(str);
if (s) { if (s) {
if (badcheck && memchr(s, '\0', len)) { if (badcheck && memchr(s, '\0', len)) {
if (raise)
rb_raise(rb_eArgError, "string for Float contains null byte"); rb_raise(rb_eArgError, "string for Float contains null byte");
else {
if (error) *error = 1;
return 0.0;
}
} }
if (s[len]) { /* no sentinel somehow */ if (s[len]) { /* no sentinel somehow */
char *p = ALLOCV(v, (size_t)len + 1); char *p = ALLOCV(v, (size_t)len + 1);
@ -3351,12 +3356,31 @@ rb_str_to_dbl(VALUE str, int badcheck)
s = p; s = p;
} }
} }
ret = rb_cstr_to_dbl(s, badcheck); ret = rb_cstr_to_dbl_raise(s, badcheck, raise, error);
if (v) if (v)
ALLOCV_END(v); ALLOCV_END(v);
return ret; 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 */ /*! \cond INTERNAL_MACRO */
#define fix2dbl_without_to_f(x) (double)FIX2LONG(x) #define fix2dbl_without_to_f(x) (double)FIX2LONG(x)
#define big2dbl_without_to_f(x) rb_big2dbl(x) #define big2dbl_without_to_f(x) rb_big2dbl(x)
@ -3390,7 +3414,7 @@ implicit_conversion_to_float(VALUE val)
} }
static int static int
to_float(VALUE *valp) to_float(VALUE *valp, int raise_exception)
{ {
VALUE val = *valp; VALUE val = *valp;
if (SPECIAL_CONST_P(val)) { if (SPECIAL_CONST_P(val)) {
@ -3401,7 +3425,7 @@ to_float(VALUE *valp)
else if (FLONUM_P(val)) { else if (FLONUM_P(val)) {
return T_FLOAT; return T_FLOAT;
} }
else { else if (raise_exception) {
conversion_to_float(val); conversion_to_float(val);
} }
} }
@ -3423,6 +3447,42 @@ to_float(VALUE *valp)
return T_NONE; 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. * Equivalent to \c Kernel\#Float in Ruby.
* *
@ -3432,16 +3492,8 @@ to_float(VALUE *valp)
VALUE VALUE
rb_Float(VALUE val) rb_Float(VALUE val)
{ {
switch (to_float(&val)) { return rb_convert_to_float(val, TRUE);
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);
}
static VALUE FUNC_MINIMIZED(rb_f_float(VALUE obj, VALUE arg)); /*!< \private */
/* /*
* call-seq: * call-seq:
@ -3459,9 +3511,12 @@ static VALUE FUNC_MINIMIZED(rb_f_float(VALUE obj, VALUE arg)); /*!< \private */
*/ */
static VALUE 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 static VALUE
@ -3482,7 +3537,7 @@ numeric_to_float(VALUE val)
VALUE VALUE
rb_to_float(VALUE val) rb_to_float(VALUE val)
{ {
switch (to_float(&val)) { switch (to_float(&val, TRUE)) {
case T_FLOAT: case T_FLOAT:
return val; 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("format", rb_f_sprintf, -1); /* in sprintf.c */
rb_define_global_function("Integer", rb_f_integer, -1); 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("String", rb_f_string, 1);
rb_define_global_function("Array", rb_f_array, 1); rb_define_global_function("Array", rb_f_array, 1);

View File

@ -783,6 +783,7 @@ class TestFloat < Test::Unit::TestCase
assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000'))
assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?) assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?)
assert_raise(TypeError) { Float(nil) } assert_raise(TypeError) { Float(nil) }
assert_raise(TypeError) { Float(:test) }
o = Object.new o = Object.new
def o.to_f; inf = Float::INFINITY; inf/inf; end def o.to_f; inf = Float::INFINITY; inf/inf; end
assert_predicate(Float(o), :nan?) assert_predicate(Float(o), :nan?)
@ -793,6 +794,43 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}} assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}}
end 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 def test_num2dbl
assert_raise(ArgumentError, "comparison of String with 0 failed") do assert_raise(ArgumentError, "comparison of String with 0 failed") do
1.0.step(2.0, "0.5") {} 1.0.step(2.0, "0.5") {}