2009-06-23 33 views
28

Có cách nào đơn giản chỉ kiểm tra xem giá trị chuỗi có phải là giá trị float hợp lệ hay không. Gọi to_f trên một chuỗi sẽ chuyển đổi nó thành 0.0 nếu nó không phải là một giá trị số. Và sử dụng Float() đặt ra một ngoại lệ khi nó được truyền một chuỗi float không hợp lệ gần với những gì tôi muốn, nhưng tôi không muốn xử lý các ngoại lệ bắt. Những gì tôi thực sự muốn là một phương pháp như nan? mà không tồn tại trong lớp Float, nhưng điều đó không giúp bởi vì một chuỗi không phải là số không thể được chuyển đổi thành float mà không bị thay đổi thành 0.0 (sử dụng to_f).Xác định xem một chuỗi có phải là giá trị float hợp lệ không

"a".to_f => 0.0 

"a".to_f.nan? => false 

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

Có giải pháp đơn giản cho việc này hay tôi cần phải viết mã để kiểm tra xem chuỗi có phải là giá trị float hợp lệ không?

Trả lời

26

Một thực tế thú vị về thế giới của Ruby là sự tồn tại của dự án Rubinius, mà thực hiện Ruby và thư viện tiêu chuẩn của nó chủ yếu ở tinh khiết Ruby. Kết quả là, họ có một Ruby thực hiện tinh khiết của Kernel # phao, trông giống như:

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 

này cung cấp cho bạn với một biểu thức chính quy phù hợp với công việc nội bộ của Ruby làm khi nó chạy nổi(), nhưng không có ngoại lệ. Vì vậy, bây giờ bạn có thể làm:

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

Những điều tốt đẹp về giải pháp này là vì Rubinius chạy, và vượt qua RubySpec, bạn biết regex này xử lý các edge-ca rằng Ruby tự xử lý, và bạn có thể gọi to_f trên Chuỗi mà không có bất kỳ sợ hãi!

+0

Câu trả lời hay! Lưu ý: Regex này đã phát triển một chút trong việc triển khai của Rubinius, xem thông số kỹ thuật tại https://github.com/rubinius/rubinius/blob/master/spec/ruby/core/string/to_f_spec.rb để biết chi tiết. Hãy nhớ rằng nếu bạn đang sử dụng điều này để xác nhận người dùng nhập vào, bạn có thể muốn bỏ hỗ trợ cho dấu gạch dưới và chỉ sử dụng regex Rubinius 'để cảm hứng :) – captainpete

+0

Correction, Rubinius vẫn sử dụng cùng một regex cho Float(). Tìm mã tại https://github.com/rubinius/rubinius/blob/master/kernel/common/kernel19.rb – captainpete

34

Dưới đây là một cách:

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 

Nếu bạn muốn tránh những con khỉ-vá của String, bạn luôn có thể làm cho này một phương pháp học của một số mô-đun bạn kiểm soát, tất nhiên:

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

nên không tránh sử dụng cứu hộ trong đó là hình thức sửa đổi? – Benjamin

+1

Câu trả lời hay. Như một gợi ý nhỏ, tôi sẽ tránh sử dụng phủ định kép vì nó được đề xuất [ở đây] (http://www.rubydoc.info/github/bbatsov/rubocop/Rubocop/Cop/Style/DoubleNegation) –

1

Tôi đã cố thêm cụm từ này làm nhận xét nhưng dường như không có định dạng nào trong nhận xét:

mặt khác, tại sao không chỉ sử dụng như chức năng chuyển đổi của bạn, như

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

mà tất nhiên là những gì bạn không muốn làm ngay từ đầu. Tôi đoán câu trả lời là, "bạn phải viết chức năng của riêng bạn nếu bạn thực sự không muốn sử dụng xử lý ngoại lệ, nhưng, tại sao bạn sẽ làm điều đó?"

+1

Tôi chỉ muốn rõ ràng rằng việc sử dụng 0.0/0.0 là một hack bẩn nhưng nếu bạn muốn nhận được NaN nó hiện là cách duy nhất (mà tôi biết). Nếu đó là chương trình của tôi, tôi sẽ mạnh mẽ xem xét sử dụng nil thay thế. – Sam

3

Umm, nếu bạn không muốn trường hợp ngoại lệ thì có lẽ:

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

Kể từ OP đặc biệt yêu cầu một giải pháp mà không ngoại lệ. Regexp giải pháp dựa trên là nhẹ chậm:

 
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 

Kết quả:

 
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

Không phải là Float đúc một thói quen bản địa trong khi regex trên chậm hơn nhiều? –

+0

"Regexp dựa trên giải pháp là hơi chậm" - kiểm tra xem con số của bạn một lần nữa 3/5 tương đương với 60%. Tôi sẽ không gọi mất 40% như một sự sụt giảm biên. –

+0

Ngoài ra, hãy nhớ rằng nếu quá trình truyền của bạn sẽ tăng ngoại lệ thường xuyên hơn thì không, nó sẽ chậm hơn nhiều so với regexp. Điều này là do giải cứu từ một ngoại lệ rất chậm, như hiển thị ở đây: http://www.simonecarletti.com/blog/2010/01/how-slow-are-ruby-exceptions/ –

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

NB 'thay thế nhanh hơn' sẽ trả về false cho '5.00' – Lambart

3

tôi thấy các cuộc thảo luận chưa được giải quyết trên cast + ngoại lệ vs regex và tôi nghĩ rằng tôi sẽ cố gắng để mọi thứ chuẩn mực và tạo ra một câu trả lời khách quan:

Đây là nguồn cho các trường hợp tốt nhất và tồi tệ nhất của mỗi phương pháp cố gắng ở đây:

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 

với những kết quả sau đây với 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) 

Vì chúng ta chỉ xử lý các thuật toán thời gian tuyến tính nên tôi nghĩ chúng ta sử dụng các phép đo thực nghiệm để tạo khái quát. Nó là đồng bằng để thấy rằng regex là phù hợp hơn và sẽ chỉ dao động một chút dựa trên độ dài của chuỗi thông qua. Các diễn viên rõ ràng là nhanh hơn khi không có thất bại, và chậm hơn nhiều khi có thất bại.

Nếu chúng ta so sánh thời gian thành công, chúng ta có thể thấy rằng trường hợp đúc tốt nhất là khoảng .3 giây nhanh hơn so với trường hợp tốt nhất regex. Nếu chúng ta chia số lượng này cho khoảng thời gian trong trường hợp xấu nhất, chúng ta có thể ước tính có bao nhiêu lần chạy nó sẽ phá vỡ ngay cả với các ngoại lệ làm chậm quá trình cast xuống để phù hợp với tốc độ regex. Khoảng 6 giây dived bởi .3 cho chúng ta khoảng 20. Vì vậy, nếu hiệu suất các vấn đề và bạn mong đợi ít hơn 1 trong 20 của thử nghiệm của bạn để thất bại sau đó sử dụng cast + ngoại lệ.

JRuby 1.7.4 có kết quả hoàn toàn khác nhau:

   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 chỉ là nhẹ nhanh hơn trong trường hợp tốt nhất (khoảng 10%). Giả sử sự khác biệt này là thích hợp để làm cho khái quát hóa (tôi không nghĩ nó là), sau đó điểm hòa vốn là một nơi nào đó giữa 200 và 250 chạy với chỉ 1 gây ra một ngoại lệ.

Vì vậy, ngoại lệ chỉ nên được sử dụng khi những điều thực sự đặc biệt xảy ra, đây là quyết định cho bạn và codebase của bạn. Khi họ không được sử dụng mã họ đang ở có thể được đơn giản và nhanh hơn.

Nếu hiệu suất không quan trọng, có lẽ bạn nên theo dõi bất kỳ quy ước nào mà bạn nhóm hoặc mã cơ sở đã có và bỏ qua toàn bộ câu trả lời này.

2

Hãy thử điều này

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 

này hỗ trợ 1.5, 5, 123.456, 1_000 nhưng không 1 000, 1,000, vv (ví dụ tương tự như String#to_f).

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

Nguồn: https://github.com/ruby/ruby/blob/trunk/object.c#L2934-L2959

Các vấn đề liên quan