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

发布于 2024-12-23 18:59:53 字数 669 浏览 1 评论 0原文

在 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

In Ruby, 0.0 * -1 == -0.0.

I have an application where I multiply a bunch of Float objects with -1, but I don't like the -0.0 in the output, since it's confusing.

Is there a smart way of making Float#to_s output 0.0 instead of -0.0?

I'm completely fine with running every Float object through some kind of scrubber/helper method, but the following just tends to make me even more confused:

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

UPDATE:

To be more precise on what I'm looking for, I want a solution that I can run on a whole bunch of floats, some of which will be negative, some positive. The negative ones should remain negative unless they're negative zeroes, i.e. -0.0.

Examples:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

浪漫之都 2024-12-30 18:59:53

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

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

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

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

There is actually a solution which does not require a condition.

def clean_output(value)
  value + 0
end

output:

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

I don't actually like this solution better than the one I accepted, because of lack of clarity. If I'd see this in a piece of code I didn't write myself, I'd wonder why you'd want to add zero to everything.

It does solve the problem though, so I thought I'd share it here anyway.

娜些时光,永不杰束 2024-12-30 18:59:53

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

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)

If the code you wrote confuses you then this ought to really bend your mind:

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

With some proof:

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

I don't like using nonzero? because its use-case is a bit confused. It's part of Numeric but the docs show it used as part of Comparable with the <=> operator. Plus, I'd rather test for a zero condition for this purpose because it seems more straightforward.

And, though the OP's code might appear verbose, this is another of those cases where premature optimization doesn't pay off:

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

And the results:

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)

I added a version without the to_s. These were run on my laptop, which is several years old, which is why the resulting times are higher than the previous tests:

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

And the results:

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)

To sort out what was slowing down 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

With the results:

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)
活泼老夫 2024-12-30 18:59:53

我想不出比这更好的了:

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

但这只是你的解决方案的一个变体。不过,与您不同的是,这个不会更改 value 的类型(例如,如果您传递 -0 ,它将返回 0) 。但看起来这对你来说并不重要。

如果您确定这会让您的代码更简洁,您可以将类似的方法添加到 Numeric 类中(这将使该方法可用于 FloatFixnum< /code> 和其他数字类):

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

I can't think of anything better than that:

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

but that's just a variation of your solution. Though, unlike yours this one doesn't change type of value (if, for example, you pass -0 it'll return 0). But looks like it's not important in your case.

If you're sure that'll make your code cleaner you can add method like that to Numeric class (that will make that method available for Float, Fixnum, and other numeric classes):

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

and then use it:

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

That will make easier to process an array of floats:

[-0.0, -3.0, 0.0, -0].map &:clean_to_s
# => ["0.0", "-3.0", "0.0", "0"]
日记撕了你也走了 2024-12-30 18:59:53

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

fl_num = -0.0
fl_num = fl_num.abs

fl_num = 0.0

simply just check whether answer is zero then apply abs to the value. It will convert -0.0 into 0.0

fl_num = -0.0
fl_num = fl_num.abs

fl_num = 0.0
热鲨 2024-12-30 18:59:53

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

To me, the intent of this code is a little clearer and at least in Ruby 1.9.3 it's slightly faster than @the 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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文