确定字符串是否为有效的浮点值

时间:2009-06-23 18:49:15

标签: ruby-on-rails ruby

有没有办法简单地检查字符串值是否是有效的浮点值。如果字符串不是数值,则在字符串上调用to_f会将其转换为0.0。使用Float()会在传递一个更接近我想要的无效浮点字符串时引发异常,但我不想处理捕获异常。我真正想要的是像nan这样的方法吗?它存在于Float类中,但这没有用,因为非数字字符串无法转换为float而不会更改为0.0(使用to_f)。

"a".to_f => 0.0

"a".to_f.nan? => false

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

是否有一个简单的解决方案或我是否需要编写代码来检查字符串是否是有效的浮点值?

9 个答案:

答案 0 :(得分:36)

这是一种方式:

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

如果你想避免使用String的猴子补丁,你总是可以将它作为你控制的某个模块的类方法,当然:

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

答案 1 :(得分:27)

关于Ruby世界的一个有趣的事实是Rubinius项目的存在,该项目主要在纯Ruby中实现Ruby及其标准库。因此,他们有一个纯粹的Ruby内核#Float实现,它看起来像:

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

这为您提供了一个正则表达式,它与Ruby运行Float()时所做的内部工作相匹配,但没有例外。所以你现在可以这样做:

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

这个解决方案的优点在于,由于Rubinius运行并传递RubySpec,你知道这个正则表达式处理Ruby本身处理的边缘情况,你可以毫不畏惧地调用字符串上的to_f!

答案 2 :(得分:8)

# 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

答案 3 :(得分:4)

我看到关于强制转换+例外与正则表达式的未解决的讨论,我认为我会尝试对所有内容进行基准测试并产生客观答案:

以下是最佳案例的来源以及此处尝试的每种方法的最差情况:

require "benchmark"
n = 500000

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

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

class String
  def to_float
    Float self rescue (0.0 / 0.0)
  end
end


Benchmark.bm(7) do |x|
  x.report("Using cast best case") {
    n.times do |i|
      temp_fl = "#{i + 0.5}"
      is_float?(temp_fl)
    end
  }
  x.report("Using cast worst case") {
    n.times do |i|
      temp_fl = "asdf#{i + 0.5}"
      is_float?(temp_fl)
    end
  }
  x.report("Using cast2 best case") {
    n.times do |i|
      "#{i + 0.5}".to_float
    end
  }
  x.report("Using cast2 worst case") {
    n.times do |i|
      "asdf#{i + 0.5}".to_float
    end
  }
  x.report("Using regexp short") {
    n.times do |i|
      temp_fl = "#{i + 0.5}"
      is_float_reg(temp_fl)
    end
  }
  x.report("Using regexp long") {
    n.times do |i|
      temp_fl = "12340918234981234#{i + 0.5}"
      is_float_reg(temp_fl)
    end
  }
    x.report("Using regexp short fail") {
    n.times do |i|
      temp_fl = "asdf#{i + 0.5}"
      is_float_reg(temp_fl)
    end
  }
  x.report("Using regexp long fail") {
    n.times do |i|
      temp_fl = "12340918234981234#{i + 0.5}asdf"
      is_float_reg(temp_fl)
    end
  }

end

使用mri193得到以下结果:

              user     system      total        real
Using cast best case  0.608000   0.000000   0.608000 (  0.615000)
Using cast worst case  5.647000   0.094000   5.741000 (  5.745000)
Using cast2 best case  0.593000   0.000000   0.593000 (  0.586000)
Using cast2 worst case  5.788000   0.047000   5.835000 (  5.839000)
Using regexp short  0.951000   0.000000   0.951000 (  0.952000)
Using regexp long  1.217000   0.000000   1.217000 (  1.214000)
Using regexp short fail  1.201000   0.000000   1.201000 (  1.202000)
Using regexp long fail  1.295000   0.000000   1.295000 (  1.284000)

由于我们只处理线性时间算法,我认为我们使用经验测量来进行概括。很明显,正则表达式更加一致,并且只会根据传递的字符串的长度而略微波动。当没有失败时,演员阵容显然更快,而当失败时,演员阵容更慢。

如果我们比较成功时间,我们可以看到演员表达最佳情况比正则表达式最佳情况快约0.3秒。如果我们将这个除以最坏情况下的时间量,我们就可以估算出需要多少次运行才能达到收支平衡,除非减慢转换速度以匹配正则表达式速度。大约6秒潜水.3给了我们大约20秒。因此,如果性能很重要,并且您希望测试中不到1分钟失败,那么使用强制转换+例外。

JRuby 1.7.4有完全不同的结果:

              user     system      total        real
Using cast best case  2.575000   0.000000   2.575000 (  2.575000)
Using cast worst case 53.260000   0.000000  53.260000 ( 53.260000)
Using cast2 best case  2.375000   0.000000   2.375000 (  2.375000)
Using cast2 worst case 53.822000   0.000000  53.822000 ( 53.822000)
Using regexp short  2.637000   0.000000   2.637000 (  2.637000)
Using regexp long  3.395000   0.000000   3.395000 (  3.396000)
Using regexp short fail  3.072000   0.000000   3.072000 (  3.073000)
Using regexp long fail  3.375000   0.000000   3.375000 (  3.374000)

在最好的情况下(约10%),施法时间稍微快一些。假设这种差异适合于进行推广(我认为不是这样),那么收支平衡点在200到250次运行之间,只有1次导致异常。

所以异常只应在真正特殊情况发生时使用,这是您和您的代码库的决定。当他们没有使用时,他们所处的代码可以更简单,更快。

如果性能无关紧要,您可能只需遵循团队或代码库已有的任何惯例,并忽略整个答案。

答案 4 :(得分:3)

嗯,如果你不想要例外那么可能:

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

由于OP专门要求解决方案,没有例外。基于Regexp的解决方案速度很慢:

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

结果:

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)

答案 5 :(得分:2)

试试这个

def is_float(val)
  fval = !!Float(val) rescue false
  # if val is "1.50" for instance
  # we need to lop off the trailing 0(s) with gsub else no match
  return fval && Float(val).to_s == val.to_s.gsub(/0+$/,'') ? true:false
end 

s = "1000"
is_float s
 => false 

s = "1.5"
is_float s
 => true 

s = "Bob"
is_float s
 => false

n = 1000
is_float n
 => false 

n = 1.5
is_float n
 => true

答案 6 :(得分:2)

def float?(string)
  true if Float(string) rescue false
end

这支持1.55123.4561_000但不支持1 0001,000等(例如与{{相同) 1}})。

String#to_f

来源:https://github.com/ruby/ruby/blob/trunk/object.c#L2934-L2959

答案 7 :(得分:2)

Ruby 2.6 +

Ruby 2.6在Float上添加了新的exception keyword argument

因此,现在检查字符串是否包含有效的浮点数很简单:

Float('22.241234', exception: false)
# => 22.241234

Float('abcd', exception: false)
# => nil

这里是文档的a link

答案 8 :(得分:1)

我尝试将此添加为评论,但显然评论中没有格式:

另一方面,为什么不将它用作转换函数,例如

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

这当然是你最初不想做的事情。我想答案是,“如果你真的不想使用异常处理,你必须编写自己的函数,但是,为什么要这样做呢?”