sinaptia waves

A discussion around Integer division in Ruby

Nazareno Moresco
Jan 31st, 2024

Ruby defines the behavior of the division operator between two Integer numbers with the Floor Division method, this results in all non-error operations having Integer results. To obtain a Float result, you can either use the float division method #fdiv or cast one of the operands to Float using #to_f.

Do you know these memes of JavaScript snippets where they get ridiculous results such as [] + [] being an empty string? There is even a repository called WTFJs that compiles them. Well, there is nothing like this for Ruby, because Ruby is a very intuitive language.

Then one day, I typed the following in an IRB session and this happened:

3.0.0 :001 > 1 / 2
=> 0

I was expecting the result of the division to be 0.5 or 1/2r. Why is it 0? Because 1 and 2 are Integer numbers and the division operator, when used with two Integer numbers, uses the Floor Division method which always results in an Integer number, like 0. Why was I expecting 0.5 or 1/2r then? Because in Mathematics the division between two Integer numbers is not closed on Z (set of Integer numbers), this means that the result could be found in Q - Z, Q being the set of Rational numbers, for example 0.5.

Not only I had found out this, but I had found out because this wrong assumption I had about the / operator made me write a bug, so naturally I posted a rant about this in the Slack channel and I even called it “the worst design decision of Ruby” (which is a bit dramatic in retrospective), but I was surprised by the responses of my coworkers, most of them agreed with the approach, they said, “if I operate with Integers, I want Integers as results”. There was more to this than I initially thought so I decided to write this post.

Floor Division and True Division methods

I use the terms Floor Division and True Division to refer to two methods for implementing a division between two Integer numbers.

The Floor Division method returns an Integer rounding the result toward zero. For example: 8/10 => 0 instead of 0.8 or 1. Sometimes is named Truncating Division or Integer division.

The True Division method returns the Numeric result of the division. For example: 8/10 => 0.8. Sometimes is named Floating-Point Division or Decimal Division.

The principle of least surprise

There is a story around POLA (Principle Of Least Astonishment) being falsely attributed to the Ruby language philosophy and the creator of Ruby Matz refuting it, there is a quote from Matz about it:

Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, ‘I was surprised by this feature of the language, so Ruby violates the principle of least surprise.’ Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprises me.

The matter of this post is a great example of what Matz is stating. For example, I came from doing a Mathematics course at University not so long ago where we discussed Set Theory, and it was surprising for me that the default behavior was Floor Division but for other developers coming from different recent experiences, it isn’t.

How to Force a True Division

To achieve True Division with two Integer numbers in Ruby, several methods are available. Let’s explore them:

Using #to_f

# to_f in one of the operands => Float
result = integer_1.to_f / integer_2

This method is the most popular. However, it’s not explicit in its purpose of performing a Float Division.

result = integer_1 / integer_2.to_f

This is probably well understood by seasoned Ruby devs but might be confusing for people just starting with Ruby. Use code reviews to discuss these use cases and link them to the official docs so they start getting a deeper knowledge of the language.

Using #fdiv

result = integer_1.fdiv(integer_2)

The #fdiv method is self-explanatory, making it clear that the division will result in a Float. It’s more “Googleable” but less commonly used than #to_f. The method’s name can be confusing, the ‘f’ in #fdiv stands for Float Division, but it could be mistaken for Fixnum division or Floor division.

Using #to_r

result = integer_1.to_r / integer_2

The #to_r method is slightly slower than the others and is used when working with Rational numbers, which is a more niche requirement.

Other considerations

  • Monkey Patching: An alternative approach is to monkey patch the Integer class to change the behavior of the division operator to True Division. However, this should be done with caution.
  • Literal Float Operand: If one of the operands is a literal number, you can define it as a Float by appending .0. For example:
result = 8 / 10.0

In terms of performance, the methods #to_f and #fdiv are quite similar, with #to_r being marginally slower. Given their similar performance, the choice between them should be based on readability and the specific requirements of your use case.

Whatever choice you make, the key is trying to be consistent in your choice of method across your codebase. This will help create a code culture that is easy to sustain and help newcomers to the project and the language.

Is it too late?

If tomorrow the Ruby community would agree on the True Division method for the division operator instead of the Floor Division method being a better decision, would it still make sense to change it?

Changing how the division operator works with Integer numbers would be a breaking change, it could potentially introduce bugs on hundreds and hundreds of code bases, there is an interesting issue from 12 years ago, where Matz and other developers discussed introducing it to Ruby 2.0, and even though Matz agrees at first he ends up rejecting the proposal because as he states there was “too much incompatibility”, there is also a more recent issue where developers discuss the idea of making this change in Ruby 3.0 or 4.0.

This said, Python used to have the same approach to Floor Division but they were able to change it to True Division in Python 3.0, by introducing the // operator for explicit Floor Division and providing tools to automatically migrate all code. Here is the proposal document.

It wouldn’t be feasible to use the // operator in Ruby for Floor Division because it’s already defined for empty regex, but Ruby could use the ~/ operator for it like the Dart language.

It’s important to mention that in mruby, the embeddable implementation of the Ruby language, the True Division method is used on the division operator. Here is a GitHub issue about it.

Final words

In summary, the behavior of the division operator in Ruby defaults to Floor Division. As Ruby continues to evolve, it remains an open question whether adopting True Division as the default behavior would be beneficial. Have you encountered any challenges or bugs related to this behavior? Do you think it should be changed?