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