1

Possible Duplicate:
Test if string is a number in Ruby on Rails

Currently I have this (awful) code:

def is_num(num_given)
  begin
    num_given.to_i
    worked = true
  rescue
    worked = false
  ensure
    return worked
  end
end

Which I refactored to this:

def is_num(num_given)
  num_given.to_i.is_a?(Numeric) rescue false
end

This still just doesn't feel right to me, is there a better way to do this?

Both of these implementations work fine for my purposes, I am just looking for some code euphoria.

Community
  • 1
  • 1
Josh
  • 5,631
  • 1
  • 28
  • 54

4 Answers4

2

something.is_a?(Numeric) is the way to go. Referring to your latter example, there's no need to call to_i on the input.

Note that something.is_a?(Numeric) will not work if you're looking to see if a string is a number...

rthbound
  • 1,323
  • 1
  • 17
  • 24
  • This doesn't work on strings, which I believe is what the asker is trying to get at. – jmdeldin Oct 31 '12 at 00:34
  • 1
    IMHO, no need for `is_num?` method. Refactor should remove all occurrences with simple `is_a?(Numeric)`. It is clear and concise. – Krule Oct 31 '12 at 00:34
  • @jmdeldin Yes it does `"string".is_a?(Numeric) #false` – Krule Oct 31 '12 at 00:35
  • @Krule: The OP's original code would make `is_num("123") == true`. `"123".is_a?(Numeric)` is false. – jmdeldin Oct 31 '12 at 00:37
  • @rthbound: Missed a case: `is_num?("123.asd") #=> true`. I have some unit tests down below you can grab. – jmdeldin Oct 31 '12 at 00:49
  • @jmdeldin In that case, this is a duplicate of http://stackoverflow.com/questions/5661466/test-if-string-is-a-number-in-ruby-on-rails – Krule Oct 31 '12 at 00:49
  • The question only asks how to tell if a variable is a number... not how to tell if a variable holds a string that represents a number. – rthbound Oct 31 '12 at 01:02
2

Here's another solution. It's not very Ruby-like, but that's intentional (e.g., while is faster than str.chars.each in this case).

# is a character between 0 and 9? (based on C's isdigit())
def digit?(c)
  o = c.ord
  o >= 48 && o <= 57 # '0'.ord, '9'.ord
end

# is a string numeric (i.e., represented as an integer or decimal)?
def numeric?(str)
  str = str.to_s unless str.is_a?(String)
  l = str.length
  i = 0

  while i < l
    c = str[i]
    if c == '.' || c == '-'
      i += 1
      next
    end

    return false if !digit?(c)

    i += 1
  end

  true
end

Here are the unit tests. Let me know if I missed a case. For other answerers, just change the subject block to your function.

if $0 == __FILE__
  require 'minitest/autorun'
  describe :digit? do
    %w(- + : ? ! / \ ! @ $ ^ & *).each do |c|
      it "flunks #{c}" do
        digit?(c).must_equal false
      end
    end

    %w(0 1 2 3 4 5 6 7 8 9).each do |c|
      it "passes #{c}" do
        digit?(c).must_equal true
      end
    end
  end

  describe :numeric? do
    subject { :numeric? }
    %w(0 1 9 10 18 123.4567 -1234).each do |str|
      it "passes #{str}" do
        method(subject).call(str).must_equal true
      end
    end

    %w(-asdf 123.zzz blah).each do |str|
      it "flunks #{str}" do
        method(subject).call(str).must_equal false
      end
    end

    [-1.03, 123, 200_000].each do |num|
      it "passes #{num}" do
        method(subject).call(num).must_equal true
      end
    end
  end
end
jmdeldin
  • 5,354
  • 1
  • 28
  • 21
  • That works nicely for string-only input. – rthbound Oct 31 '12 at 00:56
  • @rthbound: Yeah, I was thinking this should be a method on `String`, e.g., `"asdf".numeric?`, but I couldn't bring myself to monkeypatch it. I added in a line for non-strings though, and itdoesn't hurt performance too much either. Thanks! – jmdeldin Oct 31 '12 at 01:08
1

The functions you listed won't work:

is_num("a") #=> true

The problem is that they don't raise an error for invalid input. What you want is Integer, which will raise an error which you can rescue:

def is_num(num_given)
  !!Integer(num_given) rescue false
end

This works:

irb(main):025:0> is_num("a")
=> false
irb(main):026:0> is_num(5)
=> true
irb(main):027:0> is_num((1..2))
=> false
irb(main):028:0> is_num("3")
=> true

(There may be a more natural way to do this, though.)

Chris Salzberg
  • 27,099
  • 4
  • 75
  • 82
  • Great catch, the "!!" is a really cool technique I haven't seen before. – Josh Oct 30 '12 at 23:51
  • Just stumbled on this very similar answer here: http://railsforum.com/viewtopic.php?pid=65026#p65026 – Chris Salzberg Oct 30 '12 at 23:57
  • For completeness, I [benchmarked](https://gist.github.com/3983923) the two solutions here. This method is faster on valid input, but obviously suffers the overhead of raising an exception on invalid input. – jmdeldin Oct 31 '12 at 00:00
  • Yes but `is_num_r` does not behave as you might expect:`is_num_r(-1) #=> nil`. – Chris Salzberg Oct 31 '12 at 00:04
0

You can always use a simple regex:

def is_num(num_given)
  num_given =~ /\d+(\.\d+)?/
end
Anthony DeSimone
  • 1,994
  • 1
  • 12
  • 21