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

Ruby on rails 确定字符串是否为有效的浮点值,ruby-on-rails,ruby,Ruby On Rails,Ruby,是否有一种方法可以简单地检查字符串值是否是有效的浮点值。对字符串调用_f将把它转换为0.0(如果它不是数值)。当传递一个无效的浮点字符串时,使用Float()会引发一个异常,该字符串更接近我想要的值,但我不想处理捕获异常。我真正想要的是像nan这样的方法?它确实存在于Float类中,但这没有帮助,因为非数字字符串如果不更改为0.0(使用to_f),就无法转换为Float 是否有一个简单的解决方案,或者我是否需要编写代码来检查字符串是否是有效的浮点值?这里有一种方法: class String

是否有一种方法可以简单地检查字符串值是否是有效的浮点值。对字符串调用_f将把它转换为0.0(如果它不是数值)。当传递一个无效的浮点字符串时,使用Float()会引发一个异常,该字符串更接近我想要的值,但我不想处理捕获异常。我真正想要的是像nan这样的方法?它确实存在于Float类中,但这没有帮助,因为非数字字符串如果不更改为0.0(使用to_f),就无法转换为Float

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

这里有一种方法:

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
如果您想避免使用字符串修补程序,您可以始终将其作为您控制的某个模块的类方法,当然:

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

我试图将其添加为注释,但注释中显然没有格式:

另一方面,为什么不把它作为转换函数,比如

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

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

嗯,如果你不想使用异常,那么也许:

def is_float?(fl) fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/ end def是浮点数?(fl) fl=~/(^(\d+)(\)?(\d+))|(^(\d+)(\)(\d+))/ 结束
因为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 需要“基准” n=500000 def是浮点数?(fl) !!浮子(fl)救援错误 结束 def是浮点数(fl) fl=~/(^(\d+)(\)?(\d+))|(^(\d+)(\)(\d+))/ 结束 Benchmark.bm(7)do|x| x、 报告(“使用cast”){ n、 是的| temp_fl=“#{i+0.5}” 是否浮动?(温度浮动) 结束 } x、 报告(“使用regexp”){ n、 是的| temp_fl=“#{i+0.5}” 是浮动(临时浮动) 结束 } 结束 结果:

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) 5286代码段:主!?% 用户系统总真实值 使用cast 3.0000000.0000003.000000(3.010926) 使用regexp 5.020000 0.000000 5.020000(5.021762)
关于Ruby世界的一个有趣的事实是Rubinius项目的存在,它主要在纯Ruby中实现Ruby及其标准库。因此,他们有一个内核#Float的纯Ruby实现,如下所示:

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本身处理的边缘情况,并且您可以在字符串上调用f而不必担心

我看到了关于cast+异常与regex的未解决的讨论,我想我会尝试对一切进行基准测试,并给出一个客观的答案:

# 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
以下是此处尝试的每种方法的最佳情况和最差情况的来源:

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的结果如下:

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

如果我们比较成功的时间,我们可以看到cast最佳案例比regex最佳案例快约0.3秒。如果我们将其除以最坏情况下的时间量,我们可以估计出在异常情况下需要多少次运行才能达到收支平衡,从而降低强制转换速度以匹配正则表达式速度。从0.3往下跳大约6秒,我们得到大约20秒。因此,如果性能很重要,并且您预计测试失败的概率不到20分之一,那么使用cast+异常

JRuby 1.7.4有完全不同的结果:

在最好的情况下,Cast的速度仅略快一些(约10%)。假设这种差异适合进行概括(我不认为是这样),那么盈亏平衡点在200到250次之间,只有1次导致异常

所以,只有当真正异常的事情发生时才应该使用异常,这是您和您的代码库的决定。当它们不被使用时,它们所在的代码可以更简单、更快

如果性能无关紧要,您可能应该遵循您的团队或代码库已有的任何约定,忽略这些问题的全部答案。

试试这个

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
这支持
1.5
5
123.456
1000
,但不支持
1000
1000
等(例如,与
String#to_f
相同)

来源:

Ruby 2.6+ Ruby 2.6在
浮点
中添加了一个新的

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

Float('22.241234',异常:false)
# => 22.241234
浮动('abcd',异常:false)
#=>零

以下是文档。

我只是想澄清,使用0.0/0.0是一个肮脏的黑客行为,但如果你想得到NaN,这是目前唯一的方法(我知道)。如果它是我的程序,我会强烈地考虑使用NIL。当浮点比ReXEX更慢的时候,这个浮点不是原生的吗?“基于ReGEXP的解决方案是非常缓慢的”——再检查3/5的数字等于60%。我不会把损失40%称为边际下降。另外,请记住,如果你的施法将引发异常的频率高于不引发异常的频率,那么它将比regexp慢得多。这是因为从异常中拯救非常缓慢,如图所示:极好的答案!注意:这个正则表达式在Rubinius的实现中有了一些改进,详情请参见。还请记住,如果您使用它来验证用户输入,您可能希望省去对下划线的支持,而只使用Rubinius的正则表达式来表示灵感:)更正,Rubinius仍然使用相同的正则表达式来表示Float()。在@YehudaKatz找到代码你能解释一下你是如何重写字符串函数的吗
              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)
              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)
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
def float?(string)
  true if Float(string) rescue false
end
>> float?("1.2")
=> true
>> float?("1")
=> true
>> float?("1 000")
=> false
>> float?("abc")
=> false
>> float?("1_000")
=> true