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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1034418/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 21:23:38  来源:igfitidea点击:

Determine if a string is a valid float value

ruby-on-railsruby

提问by Ben5e

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).

有没有办法简单地检查字符串值是否是有效的浮点值。如果字符串不是数值,则对字符串调用 to_f 会将其转换为 0.0。并且使用 Float() 在传递一个无效的浮点字符串时会引发异常,该字符串更接近我想要的,但我不想处理捕获异常。我真正想要的是像nan这样的方法?它确实存在于 Float 类中,但这无济于事,因为非数字字符串不能在不更改为 0.0(使用 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?

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

采纳答案by Yehuda Katz

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:

关于 Ruby 世界的一个有趣事实是 Rubinius 项目的存在,该项目主要用纯 Ruby 实现 Ruby 及其标准库。因此,他们拥有 Kernel#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

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:

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

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!

这个解决方案的好处在于,由于 Rubinius 运行并通过 RubySpec,您知道这个正则表达式处理 Ruby 本身处理的边缘情况,并且您可以毫无顾虑地在 String 上调用 to_f !

回答by Greg Campbell

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:

如果您想避免 String 的猴子补丁,您可以始终将其设为您控制的某个模块的类方法,当然:

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

回答by Ben Sand

# 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

回答by Sqeaky

I saw the unresolved discussion on cast+exceptions vs regex and I thought I would try to benchmark everything and produce an objective answer:

我看到了有关 cast+exceptions vs regex 的未解决讨论,我想我会尝试对所有内容进行基准测试并给出一个客观的答案:

Here is the source for the best case and worst of each method attempted here:

以下是此处尝试的每种方法的最佳情况和最坏情况的来源:

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

With the following results with mri193:

使用 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)
              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)

Since we are dealing with only Linear time algorithms I think we use empirical measurements to make generalizations. It is plain to see that regex is more consistent and will only fluctuate a bit based on the length of the string passed. The cast is clearly faster when there are no failure, and much slower when there are failures.

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

If we compare the success times we can see that the cast best case is about .3 seconds faster than regex best case. If we divide this by the amount of time in the case worst case we can estimate how many runs it would take to break even with exceptions slowing the cast down to match regex speeds. About 6 seconds dived by .3 gives us about 20. So if performance matters and you expect less than 1 in 20 of your test to fail then used cast+exceptions.

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

JRuby 1.7.4 has completely different results:

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)
              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)

Cast is only marginally faster in the best case (about 10%). Presuming this difference is appropriate for making generalizations(I do not think it is), then the break even point is somewhere between 200 and 250 runs with only 1 causing an exception.

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

So exceptions should only be used when truly exceptional things happens, this is decision for you and your codebase. When they aren't used code they are in can be simpler and faster.

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

If performance doesn't matter, you should probably just following whatever conventions you team or code base already has and ignore this whole answers.

如果性能无关紧要,您可能应该只遵循您的团队或代码库已经拥有的任何约定,而忽略整个答案。

回答by Hemant Kumar

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:

由于OP特别要求无一例外的解决方案。基于正则表达式的解决方案有点慢:

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)

回答by Chris Root

Try this

尝试这个

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

回答by Dorian

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

This supports 1.5, 5, 123.456, 1_000but not 1 000, 1,000, etc. (e.g. same as String#to_f).

这支持1.55123.4561_000但不1 0001,000等等(例如相同String#to_f)。

>> float?("1.2")
=> true
>> float?("1")
=> true
>> float?("1 000")
=> false
>> float?("abc")
=> false
>> float?("1_000")
=> true

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

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

回答by Marian13

Ruby 2.6 added a new exceptionkeyword argumentto Float.

Ruby 2.6 添加了一个新的exception关键字参数Float.

So, now to check whether a string contains a valid float is as simple as:

所以,现在检查一个字符串是否包含一个有效的浮点数很简单:

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

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

回答by Sam

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?"

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