Как я могу эффективно вычистить отрицательный нулевой поплавок Руби? - PullRequest
16 голосов
/ 03 января 2012

В Ruby 0.0 * -1 == -0.0.

У меня есть приложение, в котором я умножаю кучу Float объектов на -1, но мне не нравится -0.0 в выводе, так какэто сбивает с толку.

Есть ли умный способ сделать Float#to_s вывод 0.0 вместо -0.0?

Я вполне справлюсь с выполнением каждого Float объекта через какую-тоо методе скруббер / помощник, но следующее только приводит меня в замешательство:

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

ОБНОВЛЕНИЕ:

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

Примеры:

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

Ответы [ 5 ]

13 голосов
/ 03 января 2012

На самом деле есть решение, которое не требует условия.

def clean_output(value)
  value + 0
end

вывод:

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

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

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

12 голосов
/ 03 января 2012

Если код, который вы написали, сбивает вас с толку, то это должно действительно сойти с ума:

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

С некоторыми доказательствами:

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

Мне не нравится использовать nonzero?потому что его вариант использования немного запутан.Это часть Numeric, но документы показывают, что он используется как часть Comparable с оператором <=>.Кроме того, я бы предпочел проверить нулевое условие для этой цели, потому что оно кажется более простым.

И, хотя код ОП может показаться многословным, это еще один из тех случаев, когда преждевременная оптимизация не окупается:

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

И результаты:

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)

Я добавил версию без to_s.Они были запущены на моем ноутбуке, которому несколько лет, поэтому итоговое время выше, чем в предыдущих тестах:

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

И результаты:

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)

Для сортировкичто замедлялось 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

С результатами:

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)
4 голосов
/ 03 января 2012

Я не могу придумать ничего лучшего, чем это:

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

но это всего лишь вариант вашего решения. Хотя, в отличие от вашего, этот тип не меняет value (если, например, вы передадите -0, он вернет 0). Но, похоже, это не важно в вашем случае.

Если вы уверены, что это сделает ваш код чище, вы можете добавить такой метод в класс Numeric (который сделает этот метод доступным для Float, Fixnum и других числовых классов):

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

и затем используйте его:

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

Это облегчит обработку массива с плавающей точкой:

[-0.0, -3.0, 0.0, -0].map &:clean_to_s
# => ["0.0", "-3.0", "0.0", "0"]
0 голосов
/ 16 ноября 2015

Для меня смысл этого кода немного яснее и, по крайней мере, в Ruby 1.9.3 он немного быстрее, чем у @ Tin Man's

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)
0 голосов
/ 03 января 2012

просто проверьте, равен ли ноль , затем примените abs к значению.Преобразует -0.0 в 0.0

fl_num = -0.0
fl_num = fl_num.abs

fl_num = 0.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...