2012-01-03 8 views
16

En Ruby, 0.0 * -1 == -0.0.¿Cómo puedo eliminar de manera eficiente el flotante cero negativo de Ruby?

que tienen una aplicación en la multiplico un montón de objetos con Float-1, pero no me gusta la -0.0 en la salida, ya que es confuso.

¿Hay alguna manera inteligente de hacer Float#to_s salida 0.0 en lugar de -0.0?

estoy completamente bien con el funcionamiento de todos los Float objeto a través de algún tipo de método de ayuda lavador /, pero la siguiente simplemente tiende a hacerme aún más confusa:

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

ACTUALIZACIÓN:

Para ser más preciso en lo que estoy buscando, quiero una solución que pueda ejecutar en un montón de carrozas, algunas de las cuales serán negativas, algunas positivas. Los negativos deben permanecer negativos a menos que sean ceros negativos, es decir, -0.0.

Ejemplos:

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

Respuesta

12

Si el código que escribió se confunde entonces esto debe doblar realmente su mente:

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

Con alguna prueba:

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 

no me gusta usar nonzero? debido a que su caso de uso está un poco confundido Es parte de Numeric pero los documentos muestran que se usó como parte de Comparable con el operador <=>. Además, preferiría probar una condición cero para este propósito porque parece más directo.

Y, aunque el código de la OP podría parecer prolijo, este es otro de esos casos en los que la optimización prematura no vale la pena:

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 

y los resultados:

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) 

Agregué una versión sin el to_s. Estos se llevaron a cabo en mi portátil, que tiene varios años, por lo que los tiempos resultantes son más altas que las pruebas anteriores:

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 

y los resultados:

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) 

de resolver lo que estaba desacelerando non_zero? abajo:

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 los resultados:

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

Muchas gracias por esta respuesta. Es realmente informativo. Los puntos de referencia no son tan relevantes para mi aplicación, pero es muy interesante ver que clean_to_s es mucho más lento que las otras soluciones. – Frost

+1

La diferencia no será tan grande si elimina la llamada 'to_s'. Si va a imprimir esos valores, se llamará de todos modos, pero en este punto de referencia se llama explícitamente en 'clean_to_s'. Sin embargo, 'nonzero?' + 'Abs' es más lento de todos modos, ya que implica dos llamadas al método en lugar de una en otros casos. –

+0

@ KL-7, "La diferencia no será tan grande si elimina la llamada de to_s". Agregué algunas pruebas más para ver. –

0

simplemente comprobar si la respuesta es cero luego aplicar abs al valor. Se convertirá en -0,0 0,0

fl_num = -0.0 
fl_num = fl_num.abs 

fl_num = 0.0 
+0

Claro, pero eso no va a hacer por cualquier otro valor. Como tengo un montón de flotadores, algunos de ellos pueden ser números negativos reales, y en ese caso deberían seguir así. '-3.0.abs' no es' -3.0'. Estoy buscando algo que solo afecte a un cero negativo ('-0.0'). – Frost

+0

@Martin está bien, pero puede verificar si ** value ** es cero y luego aplicar else seguirá siendo el mismo número negativo, ¿verdad? –

+0

¿Quiere decir 'if f1_num.zero ?; f1_num.abs; más; f1_num; fin'? Esa es básicamente la misma solución que escribí en la pregunta ... Supongo que una parte de mi pregunta es si puedes resolver esto sin un 'si'. – Frost

4

No puedo pensar en nada mejor que eso:

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

pero eso es sólo una variación de su solución. Aunque, a diferencia del tuyo, este no cambia el tipo de value (si, por ejemplo, pasas -0, devolverá 0). Pero parece que no es importante en tu caso.

Si está seguro de que va a hacer el código más limpio puede agregar método así para Numeric clase (que hará que el método disponible para Float, Fixnum, y otras clases numéricas):

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

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

Eso hará más fácil de procesar una serie de flotadores:

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

Hm ... y aquí estaba asumiendo que NumeriC# nonzero? se suponía que devolvería un valor booleano. – Frost

+0

No, devuelve el objeto si no es cero y 'nil' en caso contrario. Ver [docs] (http://www.ruby-doc.org/core-1.9.3/Numeric.html#method-i-nonzero-3F). –

+0

Lo hice. Creo que soy un poco ingenuo al pensar que los métodos que terminan en '' 'deberían devolver un booleano real, pero obviamente es muy útil de esta manera. – Frost

13

En realidad, existe una solución que no requiere una condición.

def clean_output(value) 
    value + 0 
end 

de salida:

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

yo no realmente como esta solución mejor que la que he aceptado, debido a la falta de claridad. Si yo veo esto en un código que no escribí, me pregunto por qué querría agregar cero a todo.

Resuelve el problema, así que pensé en compartirlo aquí de todos modos.

+1

Si las personas solo usaran el código con el que están familiarizados, no estaría aprendiendo mucho. Solo digo ... – cvshepherd

0

Para mí, la intención de este código es un poco más claro y por lo menos en Ruby 1.9.3 es ligeramente más rápido que el de @ el hombre de hojalata

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) 
Cuestiones relacionadas