2009-06-23 71 views
28

有没有办法简单地检查字符串值是否是有效的浮点值。如果字符串不是数值,则调用to_f可将其转换为0.0。而当使用Float()传递一个更接近我想要的无效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" 

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

回答

26

关于Ruby世界一个有趣的事实是Rubinius项目,它实现了Ruby和标准库主要集中在纯Ruby的存在。其结果是,他们有一个纯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 

这为您提供了一个正则表达式的内部工作,当它运行浮点()红宝石不匹配,但没有例外。所以,你现在可以这样做:

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

这个解决方案的好处是,由于Rubinius的运行,并通过RubySpec,你就知道这正则表达式处理边缘案件红宝石本身处理,并且可以调用to_f上字符串没有任何恐惧!

34

这里有一种方法:

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 
+0

你不应该避免使用救援在它的修饰形式? – Benjamin 2017-08-16 19:44:43

+1

很好的答案。作为一个小建议,我会避免使用双重否定,因为它建议[这里](http://www.rubydoc.info/github/bbatsov/rubocop/Rubocop/Cop/Style/DoubleNegation) – 2018-01-05 19:43:09

1

我试图添加这个作为评论,但显然没有格式的评论:

另一方面,为什么不只是使用它作为你的转换函数,如

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

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

+1

我只是想澄清0.0/0.0的使用是一个肮脏的黑客,但如果你想获得NaN,它是目前唯一的方法(我知道)。如果是我的程序,我会强烈考虑使用nil来代替。 – Sam 2009-06-23 19:22:20

3

嗯,如果你不想例外,那么也许:

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

由于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 

结果:

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

不是Float铸造本机例程,而上述正则表达式要慢得多? – 2009-06-24 13:32:38

+0

“基于正则表达式的解决方案稍微慢一点” - 再次检查您的数字3/5等于60%。我不会将失去40%作为边际下降。 – 2009-06-24 16:05:55

9
# 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

我看到投+异常VS正则表达式悬而未决的讨论,我想我会尽力基准一切,并产生一个客观的答案:

这里是最好的情况下,源和最差每种方法的这里尝试:

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。因此,如果性能很重要,并且您预计在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) 

Cast是仅稍快在最好的情况下(约10%)。假设这种差异适合进行概括(我不认为它是这样),那么盈亏平衡点在200到250之间,只有1个引起异常。

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

如果性能并不重要,您应该遵循您的团队或代码库已有的任何约定,并忽略这整个答案。

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 
1
def float?(string) 
    true if Float(string) rescue false 
end 

这支持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 

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

相关问题