mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
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:
parent
2cfc5b03da
commit
2993b4423d
2 changed files with 205 additions and 112 deletions
279
object.c
279
object.c
|
@ -3228,6 +3228,90 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj)
|
|||
return rb_convert_to_integer(arg, base, opts_exception_p(opts));
|
||||
}
|
||||
|
||||
static double
|
||||
rb_cstr_to_dbl_raise(const char *p, int badcheck, int raise, int *error)
|
||||
{
|
||||
const char *q;
|
||||
char *end;
|
||||
double d;
|
||||
const char *ellipsis = "";
|
||||
int w;
|
||||
enum {max_width = 20};
|
||||
#define OutOfRange() ((end - p > max_width) ? \
|
||||
(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;
|
||||
}
|
||||
|
||||
d = strtod(p, &end);
|
||||
if (errno == ERANGE) {
|
||||
OutOfRange();
|
||||
rb_warning("Float %.*s%s out of range", w, p, ellipsis);
|
||||
errno = 0;
|
||||
}
|
||||
if (p == end) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (errno == ERANGE) {
|
||||
errno = 0;
|
||||
OutOfRange();
|
||||
rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Parses a string representation of a floating point number.
|
||||
*
|
||||
|
@ -3242,82 +3326,44 @@ rb_f_integer(int argc, VALUE *argv, VALUE obj)
|
|||
double
|
||||
rb_cstr_to_dbl(const char *p, int badcheck)
|
||||
{
|
||||
const char *q;
|
||||
char *end;
|
||||
double d;
|
||||
const char *ellipsis = "";
|
||||
int w;
|
||||
enum {max_width = 20};
|
||||
#define OutOfRange() ((end - p > max_width) ? \
|
||||
(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;
|
||||
}
|
||||
|
||||
d = strtod(p, &end);
|
||||
if (errno == ERANGE) {
|
||||
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 (*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;
|
||||
}
|
||||
}
|
||||
if (errno == ERANGE) {
|
||||
errno = 0;
|
||||
OutOfRange();
|
||||
rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis);
|
||||
}
|
||||
return d;
|
||||
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;
|
||||
double ret;
|
||||
VALUE v = 0;
|
||||
|
||||
StringValue(str);
|
||||
s = RSTRING_PTR(str);
|
||||
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);
|
||||
MEMCPY(p, s, char, len);
|
||||
p[len] = '\0';
|
||||
s = p;
|
||||
}
|
||||
}
|
||||
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.
|
||||
*
|
||||
|
@ -3332,29 +3378,7 @@ rb_cstr_to_dbl(const char *p, int badcheck)
|
|||
double
|
||||
rb_str_to_dbl(VALUE str, int badcheck)
|
||||
{
|
||||
char *s;
|
||||
long len;
|
||||
double ret;
|
||||
VALUE v = 0;
|
||||
|
||||
StringValue(str);
|
||||
s = RSTRING_PTR(str);
|
||||
len = RSTRING_LEN(str);
|
||||
if (s) {
|
||||
if (badcheck && memchr(s, '\0', len)) {
|
||||
rb_raise(rb_eArgError, "string for Float contains null byte");
|
||||
}
|
||||
if (s[len]) { /* no sentinel somehow */
|
||||
char *p = ALLOCV(v, (size_t)len + 1);
|
||||
MEMCPY(p, s, char, len);
|
||||
p[len] = '\0';
|
||||
s = p;
|
||||
}
|
||||
}
|
||||
ret = rb_cstr_to_dbl(s, badcheck);
|
||||
if (v)
|
||||
ALLOCV_END(v);
|
||||
return ret;
|
||||
return rb_str_to_dbl_raise(str, badcheck, TRUE, NULL);
|
||||
}
|
||||
|
||||
/*! \cond INTERNAL_MACRO */
|
||||
|
@ -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);
|
||||
|
|
|
@ -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") {}
|
||||
|
|
Loading…
Add table
Reference in a new issue