如何有效地擦除Ruby的负零浮点?

时间:2012-01-03 10:45:32

标签: ruby floating-point negative-number

在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 个答案:

答案 0 :(得分:13)

实际上有一种解决方案不需要条件。

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

由于缺乏清晰度,我实际上并不比我接受的解决方案更喜欢这种解决方案。如果我在一段代码中看到这个,我没有自己写,我想知道你为什么要为所有东西添加零。

它确实解决了这个问题,所以无论如何我想我会在这里分享它。

答案 1 :(得分:12)

如果您编写的代码让您感到困惑,那么这应该让您大吃一惊:

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 :(得分:4)

我想不出比这更好的事情:

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

但这只是解决方案的一种变体。但是,与你的不同,这个版本不会改变value的类型(例如,如果你传递-0,它将返回0)。但看起来它在你的情况下并不重要。

如果你确定能让你的代码更清晰,你可以将这样的方法添加到Numeric类(这将使该方法可用于FloatFixnum和其他数字类):

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

答案 3 :(得分:0)

只需检查答案是否为,然后将 abs 应用于该值。它会将-0.0转换为0.0

fl_num = -0.0
fl_num = fl_num.abs

fl_num = 0.0

答案 4 :(得分:0)

对我来说,这段代码的意图有点清晰,至少在Ruby 1.9.3中它比@This 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)