String is a number?

Anton Jenkins | June 22, 2009

I’ve been fishing around to see if Ruby has any way of telling whether a String object contains a valid number. The is_a? method looked like it might be a winner for a little while…

1
2
3
4
5
>> 34.is_a?(Numeric)
=> true

>> "rah rah".is_a?(Numeric)
=> false

So far so good. But my number is going to be stored in a String, and is_a? doesn’t actually look at the value of the String, it just checks its type.

1
2
>> "34".is_a?(Numeric)
=> false

Ah. Fail. String isn’t a numerical class so even though it contains a numerical string, it’s always going to fail that test.

What about to_i?

I found to_i a little too clever for my needs…

1
2
3
4
5
6
7
8
9
>> "34".to_i
=> 34

# All good so far...

>> "34DFDF".to_i
=> 34

# Ah. Fail.

I don’t know why I typed 34DFDF. Sounds like a scary bra size….

Rails Forum to the rescue

Next I found a thread on railsforum.com which showed how to make an is_numeric? method and I decided to take that idea and extend the Object class to include this method on all objects. The idea being you could go :

1
2
3
4
5
6
7
8
>> 34.is_numeric?
=> true

>> "34".is_numeric?
=> true

>> "34DFDF".is_numeric?
=> false

That’s perfect, so let’s write it….

Writing an is_numeric? method

I’m going to extend the Object class with this method so that it’s available everywhere. I’m using this in a rails app so I wrote the following in lib/core_extensions/object.rb

1
2
3
4
5
6
7
8
9
# lib/core_extensions/object.rb

module CoreExtensions::Object
  def is_numeric?
    true if Float(self) rescue false
  end
end

Object.send(:include, CoreExtensions::Object)

What it’s doing is seeing if the Float class can instantiate an instance of itself with the value of object. If object can’t be parsed to a Float then it throws an exception. If an exception is rescued then we know the object can’t be numerical so we return false. Simples.

Now we just need to require our little extension at the end of our environment.rb:

1
2
3
# config/environment.rb

require 'core_extensions/object'

... and we’re ready to go!

1
2
3
4
5
6
7
8
>> 34.is_numeric?
=> true
>> "I'm not a number".is_numeric?
=> false
>> "34".is_numeric?
=> true
>> "34DFDF".is_numeric?
=> false

Perfect.

Why extend Object and not String?

I had a ponder about that and figured it wouldn’t hurt if is_numeric? was called on non strings. By extending Object I can do things such as 34.is_numeric? which isn’t too shoddy. And I’m guessing if something nasty happens the rescue should catch anything bad. But if you can think of a good case for making it a String only method then please feel free to comment.

Also if there’s a better way of checking for numericalness then please post that in the comments. I couldn’t find anything built in to Ruby but I must admit I found that surprising.

Comments

  1. dohzya |

    I think adding class methods to Numeric would be a better idea than changing the Object class... You will be sure every libraries will work with your change

    Example, 2 methods, for testing is an object is a number or if it represents a number

    1
    2
    
    Numeric.number?("42")        # => false
    Numeric.represents_number?("42") # => true
    

  2. http://antonjenkins.myopenid.com |

    @dohzya, thanks for taking the time to comment.

    I can see how your method is safer and cleaner from a sense that numerical based functionality is grouped with the Numeric class.

    But from an aesthetics point of view I prefer...

    1
    2
    3
    4
    5
    
    >> "42".is_a?(Numeric) 
    => false
    
    >> "42".is_numeric?
    => true
    

    compared to

    1
    2
    3
    4
    5
    
    >> Numeric.number?("42")
    => false
    
    >> Numeric.represents_number?("42")
    => true
    

    Although now I've written the two side by side I think perhaps there's a case for renaming the is_numeric? method to something like represents_number? because I'm not literally checking to see if the object is Numeric, which is what the name implies. It could be misleading.

    Food for thought...

    Does anyone know of any potential nastiness happening by putting this method in Object? I'm assuming that the rescue would catch anything unexpected? If there is risk involved with having the method in Object then perhaps putting a class method in Numeric is the best answer?

  3. Ruben |

    Just a quick observation: using Float(obj) works if the obj method has a to_f method. Usually, when this method is defined, is because it acts as numeric in some way

    1
    2
    3
    4
    5
    
    >> Time.now.is_numeric?
    => true
    
    >> Time.now.to_f
    => 1245676360.47856
    

    but it may not be exactly what you want.

  4. http://antonjenkins.myopenid.com |

    Good point Ruben. Maybe some type checking in the method could eradicate that...

    1
    2
    3
    4
    
      def is_numeric?
        return true if self.is_a?(Numeric) or (self.is_a?(String) and Float(self)) rescue return false
        return false
      end
    

    So now we're constraining the method to only work with Numeric and String classes. If it isn't one of these classes it returns false.

    I'm not sure that's the most elegant way of writing the method but it cures the problem with the Time class you've pointed out. Thanks for bringing that one up.

  5. http://antonjenkins.myopenid.com |

    This is a little neater than above...

    1
    2
    3
    4
    5
    
      def represents_number?
        self.is_a?(Numeric) or (self.is_a?(String) and Float(self)) ? true : false
      rescue
        false
      end
    

    Edit : Changed method name to represents_number? as it's a more accurate name for what the method is doing.

  6. Eric Anderson |

    I have defined a similar method that seems to work well for me but haven't tested it extensively so I welcome any problems anyone sees with this:

    1
    2
    3
    4
    5
    
    class String
      def is_number?
        to_i.to_s == self
      end
    end
    

    So the basically idea is if it can go to an integer and back to a string and still be the same value then it is a number.

  7. Vít Ondruch |

    Another variant ....

    def is_numeric?
    self.is_a?(Numeric) or (self.is_a?(String) and !!Float(self))
    rescue
    false
    end

  8. John Schank |

    @Eric

    That doesn't seem to work for floats.

    i.e. "0.4".is_number? => false

  9. http://antonjenkins.myopenid.com |

    @Eric, I'm guessing because it uses to_i it isn't going to work with floats, as John points out.

    @Vit, nice little addition. I didn't think of using the result of the condition as the return value and it gets rid of that ternary operator. I don't think we'll be able to get it much neater than that!

  10. Joe Grossberg |

    When dealing with integer-like strings, what's wrong with: ... foo.to_i.to_s == foo ... ?

    Also, don't monkeypatch Object, for God's sake.

  11. http://antonjenkins.myopenid.com |

    > what's wrong with: ... foo.to_i.to_s == foo ... ?

    It's not the most self documenting statement in the world. Unless you comment that well the average noob wouldn't have a clue what's going on. Plus, as pointed out above, it falls over as soon as foo becomes a float which could be a problem depending on your situation.

    > Also, don't monkeypatch Object, for God's sake.

    @Joe, I'm interested why you think it would be such a bad idea in this case. If you've read the other comments I'm now constraining the method to only work with instances of String or Numeric so we shouldn't have any unexpected behaviour when called on different classes.

    I hear a lot of people being wary of patching at high levels such as Object and sometimes I can see their point. But in this instance I'm not sure I see any massive disadvantage and would be really interested in what people see the pitfalls as being. I appreciate the advice not to do it, I'm just curious why not.

Post a comment


(lesstile enabled - surround code blocks with ---)