summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormrkn <mrkn@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-03-15 07:19:45 +0000
committermrkn <mrkn@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-03-15 07:19:45 +0000
commit2993b4423d0648af7652316505a23a074072a517 (patch)
tree2fee081c3c1125baf7b12e902708adcbef312e49
parent2cfc5b03dac80a92de2dc7a17be4b3cfff92adf7 (diff)
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
-rw-r--r--object.c217
-rw-r--r--test/ruby/test_float.rb38
2 files changed, 174 insertions, 81 deletions
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);
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") {}