Определите, является ли строка допустимым значением float
Есть ли способ просто проверить, является ли строковое значение допустимым значением float. Вызов to_f в строке преобразует его в 0.0, если это не числовое значение. И использование Float() вызывает исключение, когда ему передается некорректная строка с плавающей точкой, которая ближе к тому, что я хочу, но я не хочу обрабатывать исключения catching. Я действительно хочу, например, такой метод, как 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"
Есть ли простое решение для этого или мне нужно написать код, чтобы проверить, является ли строка допустимым значением float?
Ответы
Ответ 1
Интересным фактом о мире Ruby является существование проекта Rubinius, который реализует Ruby и его стандартную библиотеку в основном в чистом Ruby. В результате у них есть чистая реализация Ruby ядра N 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 на String!
Ответ 2
Здесь один из способов:
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
Если вы хотите избежать патча monkey-String, вы всегда можете сделать это методом класса для некоторого модуля, которым вы управляете, конечно:
module MyUtils
def self.valid_float?(str)
!!Float(str) rescue false
end
end
MyUtils.valid_float?("a") #false
Ответ 3
# 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
Ответ 4
Умм, если вы не хотите исключений, то возможно:
def is_float?(fl)
fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
end
Так как ОП специально попросил решение без исключений. Решение на основе 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
Я видел нерешенную дискуссию о cast + exceptions vs regex, и я думал, что попытаюсь сравнить все и дать объективный ответ:
Вот источник наилучшего случая и худшего из каждого метода, который вы здесь делали:
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)
Поскольку мы имеем дело только с алгоритмами линейного времени, я думаю, что мы используем эмпирические измерения для обобщения. Очевидно, что регулярное выражение более согласованное и будет только колебаться в зависимости от длины переданной строки. Литье заметно быстрее, когда нет сбоев и намного медленнее, когда есть сбои.
Если мы сравним время успеха, мы увидим, что наилучший случай отливки примерно на 3,3 секунды быстрее, чем в случае с регулярным выражением. Если мы разделим это на количество времени в случае наихудшего случая, мы можем оценить, сколько прогонов потребуется для разрыва даже при исключениях, замедляющих сбрасывание, чтобы соответствовать скоростям регулярных выражений. Около 6 секунд, ныряемых на .3, дают нам около 20. Так что, если производительность имеет значение, и вы ожидаете, что менее 1 из 20 вашего теста не сработает, используйте литые + исключения.
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, вызывая исключение.
Таким образом, исключения должны использоваться только тогда, когда происходят действительно исключительные вещи, это решение для вас и вашей кодовой базы. Когда они не используются, код, в котором они находятся, может быть проще и быстрее.
Если производительность не имеет значения, вы должны, вероятно, просто следовать любым соглашениям, которые у вас есть в команде или базе кода, и игнорировать все ответы.
Ответ 6
Попробуйте это
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
Ответ 7
Я попытался добавить это как комментарий, но, видимо, в комментариях нет форматирования:
с другой стороны, почему бы просто не использовать это как функцию преобразования, например
class String
def to_float
Float self rescue (0.0 / 0.0)
end
end
"a".to_float.nan? => true
который, конечно же, вы не хотели делать в первую очередь. Я предполагаю, что ответ: "вам нужно написать свою собственную функцию, если вы действительно не хотите использовать обработку исключений, но зачем вам это делать?"
Ответ 8
def float?(string)
true if Float(string) rescue false
end
Это поддерживает 1.5
, 5
, 123.456
, 1_000
, но не 1 000
, 1,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