Как я могу эффективно счищать Ruby отрицательный ноль? - программирование
Подтвердить что ты не робот

Как я могу эффективно счищать Ruby отрицательный ноль?

В 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
4b9b3361

Ответ 1

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

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 с оператором <=>. Кроме того, я бы предпочел проверить нулевое условие для этой цели, потому что это кажется более простым.

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

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)

Ответ 2

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

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

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

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

Ответ 3

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

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 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"]

Ответ 4

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

fl_num = -0.0
fl_num = fl_num.abs

fl_num = 0.0

Ответ 5

Для меня цель этого кода немного понятна и, по крайней мере, в 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)