views:

785

answers:

5

Is there a way to simply check if a string value is a valid float value. Calling to_f on a string will convert it to 0.0 if it is not a numeric value. And using Float() raises an exception when it is passed an invalid float string which is closer to what I want, but I don't want to handle catching exceptions. What I really want is a method such as nan? which does exist in the Float class, but that doesn't help because a non-numeric string cannot be converted to a float without being changed to 0.0 (using to_f).

"a".to_f => 0.0

"a".to_f.nan? => false

Float("a") => ArgumentError: invalid value for Float(): "a"

Is there a simple solution for this or do I need to write code to check if a string is a valid float value?

+6  A: 

Here's one way:

class String
  def valid_float?
    # The double negation turns this into an actual boolean true - if you're 
    # okay with "truthy" values (like 0.0), you can remove it.
    !!Float(self) rescue false
  end
end

"a".valid_float? #false
"2.4".valid_float? #true

If you want to avoid the monkey-patch of String, you could always make this a class method of some module you control, of course:

module MyUtils
  def self.valid_float?(str)
    !!Float(str) rescue false
  end
end
MyUtils.valid_float?("a") #false
Greg Campbell
A: 

I tried to add this as a comment but apparently there is no formatting in comments:

on the other hand, why not just use that as your conversion function, like

class String
  def to_float
    Float self rescue (0.0 / 0.0)
  end
end
"a".to_float.nan? => true

which of course is what you didn't want to do in the first place. I guess the answer is, "you have to write your own function if you really don't want to use exception handling, but, why would you do that?"

Sam
I just wanted to be clear that the use of 0.0 / 0.0 is a dirty hack but if you want to get NaN it is currently the only way (that I know of). If it were my program I would strongly consider using nil instead.
Sam
A: 

Umm, if you don't want exceptions then perhaps:

def is_float?(fl)
   fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
end

Since OP specifically asked for a solution without exceptions. Regexp based solution is marginally slow:

require "benchmark"
n = 500000

def is_float?(fl)
  !!Float(fl) rescue false
end

def is_float_reg(fl)
  fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
end

Benchmark.bm(7) do |x|
  x.report("Using cast") {
    n.times do |i|
      temp_fl = "#{i + 0.5}"
      is_float?(temp_fl)
    end
  }
  x.report("using regexp") {
    n.times do |i|
      temp_fl = "#{i + 0.5}"
      is_float_reg(temp_fl)
    end
  }
end

Results:

5286 snippets:master!? % 
             user     system      total        real
Using cast  3.000000   0.000000   3.000000 (  3.010926)
using regexp  5.020000   0.000000   5.020000 (  5.021762)
Hemant Kumar
Isn't the Float cast a native routine while the regex above much slower?
Julian
"Regexp based solution is marginally slow" - check you numbers again 3/5 equates to 60%. I wouldn't call losing 40% as a marginal drop.
Chris McCauley
+1  A: 

An interesting fact about the Ruby world is the existence of the Rubinius project, which implements Ruby and its standard library mostly in pure Ruby. As a result, they have a pure Ruby implementation of Kernel#Float, which looks like:

def Float(obj)
  raise TypeError, "can't convert nil into Float" if obj.nil?

  if obj.is_a?(String)
    if obj !~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
      raise ArgumentError, "invalid value for Float(): #{obj.inspect}"
    end
  end

  Type.coerce_to(obj, Float, :to_f)
end

This provides you with a regular expression that matches the internal work Ruby does when it runs Float(), but without the exception. So you could now do:

class String
  def nan?
    self !~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
  end
end

The nice thing about this solution is that since Rubinius runs, and passes RubySpec, you know this regex handles the edge-cases that Ruby itself handles, and you can call to_f on the String without any fear!

Yehuda Katz
A: 
# Edge Cases:
# numeric?"Infinity" => true is_numeric?"Infinity" => false


def numeric?(object)
true if Float(object) rescue false
end

#Possibly faster alternative
def is_numeric?(i)
i.to_i.to_s == i || i.to_f.to_s == i
end
Ben Sand