Ruby: How to check if a String is numeric

Finally, a technical article… This one caused by a seemingly simple question on Stack Overflow, asking how to Test if string is a number.

It turns out that it’s not entirely that simple to answer, and there are lot of apparently correct ideas, that all turn out to fail closer scrutiny.

For those who just want the answer: As far as I can tell, the best method is the one proposed on RosettaCode and in the Stack Overflow question above:

class String
  def numeric?
    Float(self) != nil rescue false
  end
end

If you want some more details, continue reading.

Let’s verify our code by transforming the question to something ubiquitous. The above method String#numeric? has the following behaviour:

"42".numeric? #=> true
"-42".numeric? #=> true
"1.2".numeric? #=> true
"0".numeric? #=> true
"1.2e34".numeric? #=> true
"1_000".numeric? #=> true
"".numeric? #=> false
" ".numeric? #=> false
"a".numeric? #=> false
"-".numeric? #=> false
".".numeric? #=> false
"_".numeric? #=> false
"1.2.3".numeric? #=> false

As far as I can tell, that’s what we want. Are there any cases I’ve missed?

Performance

The method uses a rescue clause, basically for flow control. In addition to this being a dubious practice at best, it is not the best-performing method. In raw numbers it takes more than 10 times as long to handle a failing case than a successful one (on Ruby 1.8.7).

This causes some people to suggest a regular expression driven approach instead.

Using a regular expression

Digging around on the interwebs I found a regex discussed on RailsForum that we can use – at least after modifying it somewhat:

def numeric?
  match(/\A[+-]?\d+?(_?\d+)*(\.\d+e?\d*)?\Z/) == nil ? false : true
end

This behaves as expected. Unfortunately, it is awfully slow for failing strings, way worse than using the Float rescue method above.

Conclusion

In short, to check if a string is a numeric value, use this method:

class String
  def numeric?
    Float(self) != nil rescue false
  end
end

It is correct, generally faster, and definitely more readable.