Вопрос по ruby-on-rails, ruby – Определите, является ли строка допустимым значением с плавающей точкой

30

Есть ли способ просто проверить, является ли строковое значение допустимым значением с плавающей запятой. Вызов to_f для строки преобразует ее в 0.0, если это не числовое значение. И использование Float () вызывает исключение, когда ему передается недопустимая строка с плавающей точкой, которая ближе к тому, что я хочу, но я не хочу обрабатывать перехватывающие исключения. Что я действительно хочу, это такой метод, как NAN? который существует в классе Float, но это не помогает, потому что нечисловая строка не может быть преобразована в число с плавающей точкой без изменения на 0.0 (с использованием to_f).

<code>"a".to_f => 0.0

"a".to_f.nan? => false

Float("a") => ArgumentError: invalid value for Float(): "a"
</code>

Есть ли простое решение для этого или мне нужно написать код, чтобы проверить, является ли строка допустимым значением с плавающей запятой?

Ваш Ответ

8   ответов
1

но, видимо, в комментариях нет форматирования:

с другой стороны, почему бы просто не использовать это как функцию преобразования, например

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

что, конечно, то, что вы не хотели делать в первую очередь. Я предполагаю, что ответ таков: «Вы должны написать свою собственную функцию, если вы действительно не хотите использовать обработку исключений, но зачем вам это делать?»

Я просто хотел пояснить, что использование 0.0 / 0.0 - это грязный хак, но если вы хотите получить NaN, то это единственный способ (который я знаю). Если бы это была моя программа, я бы настоятельно рекомендовал вместо этого использовать nil.
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
27

который реализует Ruby и его стандартную библиотеку в основном на чистом Ruby. В результате у них есть чистая Ruby-реализация Kernel # 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 без какого-либо страха!

Исправление: Рубиниус все еще использует это же регулярное выражение для Float (). Найти код наgithub.com/rubinius/rubinius/blob/master/kernel/common/…
@YehudaKatz Не могли бы вы объяснить, как вы переопределяете функциональность строки, не расширяя ее?
Отличный ответ! Примечание: это регулярное выражение немного развилось в Rubinius & apos; реализации, см. спецификации вgithub.com/rubinius/rubinius/blob/master/spec/ruby/core/string/… для деталей. Помните также, что если вы используете это для проверки ввода пользователя, вы можете отказаться от поддержки подчеркивания и просто использовать Rubinius & apos; регулярное выражение для вдохновения :)
8
# 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
NB «более быстрая альтернатива» вернет false для "5,00";
4

что постараюсь все сравнить и дать объективный ответ:

Вот источник для лучшего случая и худшего из каждого метода, предпринятого здесь:

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 секунд быстрее, чем лучший случай для регулярного выражения. Если мы разделим это на количество времени в худшем случае, мы сможем оценить, сколько прогонов потребуется, чтобы достичь безубыточности, с исключениями, замедляющими приведение до скоростей регулярного выражения. Примерно 6 секунд, которые делятся на .3, дают нам около 20. Поэтому, если производительность имеет значение, и вы ожидаете, что менее 1 из 20 вашего теста потерпит неудачу, используйте исключения cast +.

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 вызывает исключение.

Таким образом, исключения должны использоваться только тогда, когда происходят действительно исключительные вещи, это решение для вас и вашей базы кода. Когда они не используют код, они могут быть проще и быстрее.

Если производительность не имеет значения, вам, вероятно, следует просто следовать тем соглашениям, которые у вас уже есть в команде или кодовой базе, и игнорировать все эти ответы.

3

def is_float?(fl)
   fl =~ /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
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

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)
Кроме того, имейте в виду, что если ваш кастинг будет вызывать исключения чаще, чем нет, он будет намного медленнее, чем регулярное выражение. Это потому, что спасение от исключения происходит очень медленно, как показано здесь:simonecarletti.com/blog/2010/01/how-slow-are-ruby-exceptions
«Решение на основе регулярных выражений незначительно медленно» - проверьте свои цифры снова 3/5 соответствует 60%. Я бы не назвал потерю 40% в качестве предельного падения.
Разве Float не использует нативную рутину, в то время как регулярное выражение намного медленнее?
2
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

35

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

Если вы хотите избежать обезьяньего патча String, вы всегда можете сделать метод класса некоторого модуля, которым вы управляете, конечно:

module MyUtils
  def self.valid_float?(str)
    !!Float(str) rescue false
  end
end
MyUtils.valid_float?("a") #false
Не следует ли вам избегать использования функции спасения в форме модификатора?
Хороший ответ. В качестве небольшого предложения я бы избегал использования двойного отрицания, как это было предложеноhere

Похожие вопросы