The architecture of Birmingham, UK

Mike Hindle

Express yourself clearly with positive? and negative? for numbers

Ruby, in contrast to other languages, often provides multiple ways to accomplish simple programming tasks. In pursuit of developer happiness, the Standard Library offers the opportunity to make your code appear more like English.

In this case we’ll look at comparing numbers with zero.

Instead of…

…using comparison operators:

if number > 0
  # do a thing
end

if number < 0
  # do a thing
end

Use…

…Ruby’s convenience methods on Numeric, and its subclasses, to express yourself more clearly:

if number.positive?
  # do a thing
end

if number.negative?
  # do a thing
end

Why?

I’ve discussed my preference for a similar comparator, zero?, in a previous article.

The “more English” version will be clearer and easier to reason about when you return to your code in the future. It can require a moment of concentration to unpack the orientation of the angle bracket and how it relates to the numbers and variables.

This approach to convenience methods and syntactical sugar is one of the most delightful things about Ruby.

“Ruby is designed to make programmers happy.”–Yukihiro “Matz” Matsumoto

Why not?

Some folks find this style to be too different from other languages and claim that they’ve never struggled to remember whether < is “less than” or “greater than’.

You might get push back for “performance” reasons:

require "benchmark/ips"

Benchmark.ips do |x|
  x.report("1 > 0") { 1 > 0 } #=> true
  x.report("0 > 0") { 0 > 0 } #=> false
  x.report("-1 > 0") { -1 > 0 } #=> false
  x.report("1.positive?") { 1.positive? } #=> true
  x.report("0.positive?") { 0.positive? } #=> false
  x.report("-1.positive?") { 0.positive? } #=> false

  x.report("1.0 > 0") { 1 > 0 } #=> true
  x.report("0.0 > 0") { 0 > 0 } #=> false
  x.report("-1.0 > 0") { -1 > 0 } #=> false
  x.report("1.0.positive?") { 1.positive? } #=> true
  x.report("0.0.positive?") { 0.positive? } #=> false
  x.report("-1.0.positive?") { 0.positive? } #=> false
end

For Integer:

1 > 0 31.644M (± 0.1%) i/s
0 > 0 31.625M (± 0.2%) i/s
-1 > 0 30.919M (± 0.1%) i/s
1.positive? 22.602M (± 0.2%) i/s
0.positive? 22.621M (± 0.2%) i/s
-1.0.positive? 22.676M (± 0.1%) i/s

For Float:

1.0 > 0 31.442M (± 0.2%) i/s
0.0 > 0 31.178M (± 0.1%) i/s
-1.0 > 0 30.861M (± 0.2%) i/s
1.0.positive? 22.707M (± 0.1%) i/s
0.0.positive? 22.576M (± 0.1%) i/s
-1.0.positive? 22.654M (± 0.1%) i/s

The benchmark shows that, for both Integer and Floats, the non-sugared syntax wins out.

However, the important thing to note is that in all cases I was achieving tens of millions of executions per second on my laptop. For anything other than the most extreme performance requirements, opt for the more readable version. And if performance truly matters beyond this scale, then you likely have bigger issues!

Last updated on September 11th, 2023