2012-01-03 30 views
16

Trong Ruby, 0.0 * -1 == -0.0.Làm thế nào tôi có thể quét sạch số không âm của Ruby?

Tôi có một ứng dụng mà tôi nhân một loạt các đối tượng Float với -1, nhưng tôi không thích các -0.0 trong đầu ra, vì nó gây nhầm lẫn.

Có cách nào thông minh để thực hiện Float#to_s đầu ra 0.0 thay vì -0.0?

Tôi hoàn toàn tốt với mọi đối tượng chạy Float thông qua một số loại chà sàn/phương pháp helper, nhưng sau chỉ có xu hướng làm cho tôi thậm chí bối rối hơn:

def clean_output(amount) 
    if amount.zero? 
    0.0 
    else 
    amount 
    end 
end 

UPDATE:

Để chính xác hơn về những gì tôi đang tìm kiếm, tôi muốn một giải pháp mà tôi có thể chạy trên một loạt các phao nổi, một số trong số đó sẽ là số âm, một số tích cực. Các số âm sẽ vẫn âm trừ khi chúng là số 0 âm, tức là -0.0.

Ví dụ:

clean_output(-0.0) #=> 0.0 
clean_output(-3.0) #=> -3.0 
clean_output(3.0) #=> 3.0 

Trả lời

12

Nếu mã bạn đã viết lẫn lộn bạn thì đây phải thực sự uốn cong tâm trí của bạn:

def clean_output(amount) 
    amount.zero? && 0.0 || amount 
end 

Với một số bằng chứng:

irb(main):005:0> f = 0.0 
=> 0.0 
irb(main):006:0> f.zero? && 0.0 || f 
=> 0.0 
irb(main):007:0> f = -0.0 
=> -0.0 
irb(main):008:0> f.zero? && 0.0 || f 
=> 0.0 
irb(main):009:0> f=1.0 
=> 1.0 
irb(main):010:0> f.zero? && 0.0 || f 
=> 1.0 

tôi không thích sử dụng nonzero? vì use-case của nó là một chút bối rối. Nó là một phần của Numeric nhưng các tài liệu cho thấy nó được sử dụng như một phần của Comparable với toán tử <=>. Thêm vào đó, tôi muốn thử nghiệm cho một điều kiện không cho mục đích này bởi vì nó có vẻ đơn giản hơn.

Và, mặc dù mã của OP có thể xuất hiện tiết, đây là một trong những trường hợp tối ưu hóa quá sớm không trả hết:

require 'benchmark' 

def clean_output(amount) 
    if amount.zero? 
    0.0 
    else 
    amount 
    end 
end 

def clean_output2(amount) 
    amount.zero? && 0.0 || amount 
end 

def clean_output3(value) 
    value + 0 
end 

class Numeric 
    def clean_to_s 
    (nonzero? || abs).to_s 
    end 
end 


n = 5_000_000 
Benchmark.bm(14) do |x| 
    x.report("clean_output:" ) { n.times { a = clean_output(-0.0) } } 
    x.report("clean_output2:") { n.times { a = clean_output2(-0.0) } } 
    x.report("clean_output3:") { n.times { a = clean_output3(-0.0) } } 
    x.report("clean_to_s:" ) { n.times { a = 0.0.clean_to_s  } } 
end 

Và kết quả:

ruby test.rb 
        user  system  total  real 
clean_output: 2.120000 0.000000 2.120000 ( 2.127556) 
clean_output2: 2.230000 0.000000 2.230000 ( 2.222796) 
clean_output3: 2.530000 0.000000 2.530000 ( 2.534189) 
clean_to_s:  7.200000 0.010000 7.210000 ( 7.200648) 

ruby test.rb 
        user  system  total  real 
clean_output: 2.120000 0.000000 2.120000 ( 2.122890) 
clean_output2: 2.200000 0.000000 2.200000 ( 2.203456) 
clean_output3: 2.540000 0.000000 2.540000 ( 2.533085) 
clean_to_s:  7.200000 0.010000 7.210000 ( 7.204332) 

Tôi đã thêm phiên bản không có to_s. Chúng được chạy trên máy tính xách tay của tôi, đó là một vài tuổi, đó là lý do những lần kết quả cao hơn so với các bài kiểm tra theo thời gian:

require 'benchmark' 

def clean_output(amount) 
    if amount.zero? 
    0.0 
    else 
    amount 
    end 
end 

def clean_output2(amount) 
    amount.zero? && 0.0 || amount 
end 

def clean_output3(value) 
    value + 0 
end 

class Numeric 
    def clean_to_s 
    (nonzero? || abs).to_s 
    end 

    def clean_no_to_s 
    nonzero? || abs 
    end 

end 


n = 5_000_000 
Benchmark.bm(14) do |x| 
    x.report("clean_output:" ) { n.times { a = clean_output(-0.0) } } 
    x.report("clean_output2:") { n.times { a = clean_output2(-0.0) } } 
    x.report("clean_output3:") { n.times { a = clean_output3(-0.0) } } 
    x.report("clean_to_s:" ) { n.times { a = -0.0.clean_to_s  } } 
    x.report("clean_no_to_s:") { n.times { a = -0.0.clean_no_to_s } } 
end 

Và kết quả:

ruby test.rb 
        user  system  total  real 
clean_output: 3.030000 0.000000 3.030000 ( 3.028541) 
clean_output2: 2.990000 0.010000 3.000000 ( 2.992095) 
clean_output3: 3.610000 0.000000 3.610000 ( 3.610988) 
clean_to_s:  8.710000 0.010000 8.720000 ( 8.718266) 
clean_no_to_s: 5.170000 0.000000 5.170000 ( 5.170987) 

ruby test.rb 
        user  system  total  real 
clean_output: 3.050000 0.000000 3.050000 ( 3.050175) 
clean_output2: 3.010000 0.010000 3.020000 ( 3.004055) 
clean_output3: 3.520000 0.000000 3.520000 ( 3.525969) 
clean_to_s:  8.710000 0.000000 8.710000 ( 8.710635) 
clean_no_to_s: 5.140000 0.010000 5.150000 ( 5.142462) 

Để sắp xếp ra những gì đã làm chậm xuống non_zero?:

require 'benchmark' 

n = 5_000_000 
Benchmark.bm(9) do |x| 
    x.report("nonzero?:") { n.times { -0.0.nonzero? } } 
    x.report("abs:"  ) { n.times { -0.0.abs  } } 
    x.report("to_s:" ) { n.times { -0.0.to_s  } } 
end 

với kết quả:

ruby test.rb 
       user  system  total  real 
nonzero?: 2.750000 0.000000 2.750000 ( 2.754931) 
abs:  2.570000 0.010000 2.580000 ( 2.569420) 
to_s:  4.690000 0.000000 4.690000 ( 4.687808) 

ruby test.rb 
       user  system  total  real 
nonzero?: 2.770000 0.000000 2.770000 ( 2.767523) 
abs:  2.570000 0.010000 2.580000 ( 2.569757) 
to_s:  4.670000 0.000000 4.670000 ( 4.678333) 
+0

Cảm ơn bạn rất nhiều vì câu trả lời này. Nó thực sự mang tính thông tin. Các tiêu chuẩn không thực sự có liên quan đến ứng dụng của tôi, nhưng nó rất thú vị khi thấy rằng clean_to_s chậm hơn rất nhiều so với các giải pháp khác. – Frost

+1

Sự khác biệt sẽ không lớn nếu bạn xóa cuộc gọi 'to_s'. Nếu bạn đang đi để in các giá trị đó, nó sẽ được gọi là anyway, nhưng trong tiêu chuẩn này nó được gọi là rõ ràng trong 'clean_to_s'. Mặc dù, 'nonzero?' + 'Abs' là chậm hơn nhiều khả năng vì nó liên quan đến hai cuộc gọi phương thức thay vì một trong các trường hợp khác. –

+0

@ KL-7, "Sự khác biệt sẽ không quá lớn nếu bạn xóa cuộc gọi to_s". Tôi đã thêm một số bài kiểm tra nữa để xem. –

0

đơn giản chỉ là kiểm tra xem câu trả lời là zero sau đó áp dụng abs với giá trị. Nó sẽ chuyển đổi -0,0 vào 0,0

fl_num = -0.0 
fl_num = fl_num.abs 

fl_num = 0.0 
+0

Chắc chắn, nhưng điều đó sẽ không làm cho bất kỳ giá trị khác. Vì tôi có một loạt các phao nổi, một số trong số đó có thể là số âm thực tế, và trong trường hợp đó, họ nên ở lại như vậy. '-3.0.abs' không phải là' -3.0'. Tôi đang tìm một cái gì đó mà chỉ ảnh hưởng đến một số không âm ('-0.0'). – Frost

+0

@Martin được, nhưng bạn có thể kiểm tra xem ** giá trị ** có bằng không thì áp dụng khác nó sẽ giữ nguyên số âm, phải không? –

+0

Ý bạn là 'if f1_num.zero ?; f1_num.abs; khác; f1_num; kết thúc'? Đó là giải pháp cơ bản giống như tôi đã viết trong câu hỏi ... Tôi đoán một phần của câu hỏi của tôi là liệu bạn có thể giải quyết điều này mà không có một 'if'. – Frost

4

Tôi không thể nghĩ ra bất cứ điều gì tốt hơn:

def clean_output(value) 
    value.nonzero? || value.abs 
end 

nhưng đó chỉ là một biến thể của giải pháp của bạn. Mặc dù, không giống như của bạn, cái này không thay đổi loại value (nếu, ví dụ, bạn vượt qua -0 nó sẽ trả lại 0). Nhưng có vẻ như nó không quan trọng trong trường hợp của bạn.

Nếu bạn chắc chắn rằng sẽ làm cho mã sạch của bạn, bạn có thể thêm phương pháp như vậy để Numeric lớp (mà sẽ làm cho rằng phương pháp có sẵn cho Float, Fixnum, và các lớp học số khác):

class Numeric 
    def clean_to_s 
    (nonzero? || abs).to_s 
    end 
end 

và sau đó sử dụng nó:

-0.0.clean_to_s # => '0.0' 
-3.0.clean_to_s # => '-3.0' 
# same method for Fixnum's as a bonus 
-0.clean_to_s # => '0' 

Điều đó sẽ làm cho dễ dàng hơn để xử lý một mảng phao:

[-0.0, -3.0, 0.0, -0].map &:clean_to_s 
# => ["0.0", "-3.0", "0.0", "0"] 
+0

Hm ... và ở đây tôi giả định rằng NumeriC# nonzero? được cho là sẽ trả về một giá trị boolean. – Frost

+0

Không, nó tự trả về đối tượng nếu nó không bằng 0 và 'nil' ngược lại. Xem [docs] (http://www.ruby-doc.org/core-1.9.3/Numeric.html#method-i-nonzero-3F). –

+0

Tôi đã làm. Tôi đoán tôi chỉ là một chút ngây thơ khi nghĩ rằng các phương thức kết thúc bằng '?' Nên trả về một boolean thực tế, nhưng rõ ràng là rất có thể sử dụng theo cách này. – Frost

13

Thực sự là một giải pháp không yêu cầu điều kiện.

def clean_output(value) 
    value + 0 
end 

đầu ra:

> clean_output(3.0) 
=> 3.0 
> clean_output(-3.0) 
=> -3.0 
> clean_output(-0.0) 
=> 0.0 

tôi không thực sự thích giải pháp này tốt hơn so với cái tôi chấp nhận, vì thiếu rõ ràng. Nếu tôi thấy điều này trong một đoạn mã tôi đã không viết bản thân mình, tôi tự hỏi tại sao bạn muốn thêm không cho tất cả mọi thứ.

Nó giải quyết được vấn đề mặc dù vậy, vì vậy tôi nghĩ rằng tôi muốn chia sẻ nó ở đây anyway.

+1

Nếu mọi người chỉ sử dụng mã mà họ quen thuộc, sẽ không có nhiều việc học tập xảy ra. Chỉ cần nói .. – cvshepherd

0

Đối với tôi, mục đích của mã này là rõ ràng hơn một chút và ít nhất trong Ruby 1.9.3 nó hơi nhanh hơn @the Tín Man

def clean_output4(amount) 
    amount.zero? ? 0.0 : amount 
end 

        user  system  total  real 
clean_output: 0.860000 0.000000 0.860000 ( 0.859446) 
clean_output4: 0.830000 0.000000 0.830000 ( 0.837595) 
Các vấn đề liên quan