Test if string is a number in Ruby on Rails
I have the following in my application controller:
def is_number?(object)
true if Float(object) rescue false
end
and the following condition in my controller:
if mystring.is_number?
end
The condition is throwing an undefined method
error. I'm guessing I've defined is_number
in the wrong place...?
Solution 1:
Create is_number?
Method.
Create a helper method:
def is_number? string
true if Float(string) rescue false
end
And then call it like this:
my_string = '12.34'
is_number?( my_string )
# => true
Extend String
Class.
If you want to be able to call is_number?
directly on the string instead of passing it as a param to your helper function, then you need to define is_number?
as an extension of the String
class, like so:
class String
def is_number?
true if Float(self) rescue false
end
end
And then you can call it with:
my_string.is_number?
# => true
Solution 2:
Here's a benchmark for common ways to address this problem. Note which one you should use probably depends on the ratio of false cases expected.
- If they are relatively uncommon casting is definitely fastest.
- If false cases are common and you are just checking for ints, comparison vs a transformed state is a good option.
- If false cases are common and you are checking floats, regexp is probably the way to go
If performance doesn't matter use what you like. :-)
Integer checking details:
# 1.9.3-p448
#
# Calculating -------------------------------------
# cast 57485 i/100ms
# cast fail 5549 i/100ms
# to_s 47509 i/100ms
# to_s fail 50573 i/100ms
# regexp 45187 i/100ms
# regexp fail 42566 i/100ms
# -------------------------------------------------
# cast 2353703.4 (±4.9%) i/s - 11726940 in 4.998270s
# cast fail 65590.2 (±4.6%) i/s - 327391 in 5.003511s
# to_s 1420892.0 (±6.8%) i/s - 7078841 in 5.011462s
# to_s fail 1717948.8 (±6.0%) i/s - 8546837 in 4.998672s
# regexp 1525729.9 (±7.0%) i/s - 7591416 in 5.007105s
# regexp fail 1154461.1 (±5.5%) i/s - 5788976 in 5.035311s
require 'benchmark/ips'
int = '220000'
bad_int = '22.to.2'
Benchmark.ips do |x|
x.report('cast') do
Integer(int) rescue false
end
x.report('cast fail') do
Integer(bad_int) rescue false
end
x.report('to_s') do
int.to_i.to_s == int
end
x.report('to_s fail') do
bad_int.to_i.to_s == bad_int
end
x.report('regexp') do
int =~ /^\d+$/
end
x.report('regexp fail') do
bad_int =~ /^\d+$/
end
end
Float checking details:
# 1.9.3-p448
#
# Calculating -------------------------------------
# cast 47430 i/100ms
# cast fail 5023 i/100ms
# to_s 27435 i/100ms
# to_s fail 29609 i/100ms
# regexp 37620 i/100ms
# regexp fail 32557 i/100ms
# -------------------------------------------------
# cast 2283762.5 (±6.8%) i/s - 11383200 in 5.012934s
# cast fail 63108.8 (±6.7%) i/s - 316449 in 5.038518s
# to_s 593069.3 (±8.8%) i/s - 2962980 in 5.042459s
# to_s fail 857217.1 (±10.0%) i/s - 4263696 in 5.033024s
# regexp 1383194.8 (±6.7%) i/s - 6884460 in 5.008275s
# regexp fail 723390.2 (±5.8%) i/s - 3613827 in 5.016494s
require 'benchmark/ips'
float = '12.2312'
bad_float = '22.to.2'
Benchmark.ips do |x|
x.report('cast') do
Float(float) rescue false
end
x.report('cast fail') do
Float(bad_float) rescue false
end
x.report('to_s') do
float.to_f.to_s == float
end
x.report('to_s fail') do
bad_float.to_f.to_s == bad_float
end
x.report('regexp') do
float =~ /^[-+]?[0-9]*\.?[0-9]+$/
end
x.report('regexp fail') do
bad_float =~ /^[-+]?[0-9]*\.?[0-9]+$/
end
end
Solution 3:
class String
def numeric?
return true if self =~ /\A\d+\Z/
true if Float(self) rescue false
end
end
p "1".numeric? # => true
p "1.2".numeric? # => true
p "5.4e-29".numeric? # => true
p "12e20".numeric? # true
p "1a".numeric? # => false
p "1.2.3.4".numeric? # => false
Solution 4:
As of Ruby 2.6.0, the numeric cast-methods have an optional exception
-argument [1]. This enables us to use the built-in methods without using exceptions as control flow:
Float('x') # => ArgumentError (invalid value for Float(): "x")
Float('x', exception: false) # => nil
Therefore, you don't have to define your own method, but can directly check variables like e.g.
if Float(my_var, exception: false)
# do something if my_var is a float
end
Solution 5:
Relying on the raised exception is not the fastest, readable nor reliable solution.
I'd do the following :
my_string.should =~ /^[0-9]+$/