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));
}
/*!
* 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;
@ -3269,7 +3258,12 @@ rb_cstr_to_dbl(const char *p, int badcheck)
if (p == end) {
if (badcheck) {
bad:
if (raise)
rb_invalid_str(q, "Float()");
else {
if (error) *error = 1;
return 0.0;
}
}
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)) {
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);

View File

@ -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") {}