The architecture of Birmingham, UK

image by 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.

RailsConf 2024

I'm co-chairing RailsConf 2024 in Detroit May 7–9. Come and join us 

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!

Brighton Ruby 2024

Still running UK’s friendliest, Ruby event on Friday 28th June. Ice cream + Ruby 


Last updated on September 11th, 2023 by @andycroll

An email newsletter, with one Ruby/Rails technique delivered with a ‘why?’ and a ‘how?’ every two weeks. It’s deliberately brief, focussed & opinionated.