2012-01-03 6 views
16

In Ruby, 0.0 * -1 == -0.0.Come posso pulire in modo efficiente lo zero float negativo di Ruby?

Ho una domanda in cui moltiplico un gruppo di oggetti con Float-1, ma non mi piace il -0.0 nell'output, dal momento che è fonte di confusione.

C'è un modo intelligente di produrre Float#to_s output 0.0 anziché -0.0?

Sono completamente bene con l'esecuzione di ogni Float oggetto attraverso una sorta di lavaggio/metodo di supporto, ma il seguente solo tende a farmi ancora più confuso:

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

UPDATE:

Per essere più precisi su quello che sto cercando, voglio una soluzione che possa funzionare su un gruppo di galleggianti, alcuni dei quali saranno negativi, alcuni positivi. I negativi dovrebbero rimanere negativi a meno che non siano zeri negativi, ovvero -0.0.

Esempi:

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

risposta

12

Se il codice che hai scritto ti confonde allora questo dovrebbe piegare davvero la vostra mente:

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

Con qualche prova:

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 

non mi piace usare nonzero? perché il suo caso d'uso è un po 'confuso. Fa parte di Numeric ma i documenti lo mostrano come parte di Comparable con l'operatore <=>. Inoltre, preferisco testare una condizione zero per questo scopo perché sembra più semplice.

E, anche se il codice del PO potrebbe apparire prolisso, questo è un altro di quei casi in cui l'ottimizzazione prematura non paga:

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 

E i risultati:

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) 

Ho aggiunto una versione senza lo to_s. Questi sono stati eseguiti sul mio computer portatile, che è di diversi anni, che è il motivo per cui i tempi risultanti sono superiori ai precedenti test:

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 

E i risultati:

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) 

di risolvere quello che è stato rallentando giù 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 

Con i risultati:

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

Grazie mille per questa risposta. È davvero informativo. I benchmark non sono davvero rilevanti per la mia applicazione, ma è molto interessante vedere che clean_to_s è molto più lento delle altre soluzioni. – Frost

+1

La differenza non sarà così grande se si rimuove la chiamata 'to_s'. Se si stampano i valori, verrà chiamato comunque, ma in questo benchmark viene chiamato esplicitamente in 'clean_to_s'. Anche se 'nonzero?' + 'Abs' è più lento comunque molto probabilmente perché comporta due chiamate al metodo invece di una in altri casi. –

+0

@ KL-7, "La differenza non sarà così grande se si rimuove la chiamata to_s." Ho aggiunto altri test da vedere. –

0

semplicemente basta controllare se risposta è pari a zero quindi applicare abs al valore. Si converte -0,0 in 0,0

fl_num = -0.0 
fl_num = fl_num.abs 

fl_num = 0.0 
+0

Certo, ma non va bene per qualsiasi altro valore. Dato che ho un sacco di carri, alcuni di questi potrebbero essere numeri negativi reali, e in tal caso dovrebbero rimanere così. '-3.0.abs' non è' -3.0'. Sto cercando qualcosa che riguarda solo uno zero negativo ('-0.0'). – Frost

+0

@Martin okay, ma puoi controllare se ** value ** è zero quindi applicare else rimarrà lo stesso numero negativo, giusto? –

+0

Vuoi dire "if f1_num.zero ?; f1_num.abs; altro; f1_num; FINE'? Questa è fondamentalmente la stessa soluzione che ho scritto nella domanda ... Immagino che una parte della mia domanda sia se è possibile risolvere questo senza un "se". – Frost

4

Non riesco a pensare a qualcosa di meglio:

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

ma questo è solo una variante della soluzione. Sebbene, a differenza del tuo, questo non cambierà il tipo di value (se, ad esempio, passi -0, restituirà 0). Ma sembra che non sia importante nel tuo caso.

Se sei sicuro che farà il vostro pulitore di codice è possibile aggiungere il metodo del genere per Numeric classe (che farà quel metodo disponibile per Float, Fixnum, e le altre classi numerici):

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

e poi usarlo:

-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' 

che renderà più facile per elaborare una matrice di carri:

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

Hm ... e qui stavo dando per scontato che NumeriC# diverso da zero? doveva restituire un valore booleano. – Frost

+0

No, restituisce l'oggetto stesso se non è zero e 'nil' altrimenti. Vedi [docs] (http://www.ruby-doc.org/core-1.9.3/Numeric.html#method-i-nonzero-3F). –

+0

L'ho fatto. Credo di essere solo un po 'ingenuo nel pensare che i metodi che terminano con '?' Debbano restituire un booleano reale, ma ovviamente è molto utilizzabile in questo modo. – Frost

13

C'è in realtà una soluzione che non richiede una condizione.

def clean_output(value) 
    value + 0 
end 

uscita:

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

non mi realtà come questa soluzione migliore di quello ho accettato, a causa della mancanza di chiarezza. Se vedessi questo in un pezzo di codice che non ho scritto io stesso, mi chiedevo perché dovresti aggiungere zero a tutto.

Tuttavia, risolve il problema, quindi ho pensato di condividerla comunque.

+1

Se le persone usano solo il codice con cui hanno familiarità, non ci sarebbe molto da imparare. Sto solo dicendo .. – cvshepherd

0

Per me, l'intento di questo codice è un po 'più chiaro e almeno in Ruby 1.9.3 è leggermente più veloce rispetto @the Tin Man di

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) 
Problemi correlati