Are There More Elegant Ways to Prevent Negative Numbers in Ruby

How can I efficiently scrub Ruby's negative zero float?

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)

Ruby: Change negative number to positive number?

Using abs will return the absolute value of a number

-300.abs  # 300
300.abs # 300

In Ruby, if I have an array, how can I replace any negative values with 0?

Try this

array_sample = [2, 7, 3, -5, 2, -6]

array_sample.map! { |e| e > 0 ? e : 0 }

Response

[2, 7, 3, 0, 2, 0]

Why are negative numbers rounded down after division in Ruby?

13.div(-4) == -4 and 13.modulo(-4) == -3 so that

(-4 * -4) + -3 == 13

and you get the consistent relationship

(b * (a/b)) + a.modulo(b) == a

Why is 13.div(-4) rounded to -4 and not to -3?

This is a misconception. 13.div(-4) is not really rounded at all. It is integer division, and follows self-consistent rules for working with integers and modular arithmetic. The rounding logic described in your link fits with it, and is then applied consistently when dealing with the same divmod operation when one or both the parameters are Floats. Mathematical operations on negative or fractional numbers are often extended from simpler, more intuitive results on positive integers in this kind of way. E.g. this follows similar logic to how fractional and negative powers, or non-integer factorials are created from their positive integer variants.

In this case, it's all about self-consistency of divmod, but not about rounding in general.

Ruby's designers had a choice to make when dealing with negative numbers, not all languages will give the same result. However, once it was decided Ruby would return sign of modulo result matching the divisor (as opposed to matching the division as a whole), that set how the rest of the numbers work.

Is there any rule or convention in Ruby to round down negative numbers?

Yes. Rounding a float number means to return the numerically closest integer. When there are two equally close integers, Ruby rounds to the integer furthest from 0. This is entirely separate design decision from how integer division and modulo arithmetic methods work.

If so, why is the following code not rounding down? -3.25.round() #3

I assume you mean the result to read -3. The round method does not "round down". It does "round closest". -3 is the closest integer to -3.25. Ruby's designers did have to make a choice though, what to do with -3.5.round() # -4. Some languages would instead return a -3 when rounding that number.

Difference Between two numbers without positive or negative in Ruby

You should simply return absolute value:

def diff(a, b)
(a - b).abs
end

diff(2, 5)
# => 3

When .nil leads to error and 1 does not, are there any more elegant ways to do it?

You can use zero? on Something.count

0.zero? # true

The reason for your "error" is that 0.nil? is false. 0 is not nil.

If you are testing for 0 elements in an array or a hash, use empty? on Something.

[].empty? # true
Something.empty? # true


Related Topics



Leave a reply



Submit