Eric / Brooklyn

Random 5

Web Accounts

Honeypot Bots

Stripe and Tap

Whitespace Problems

Ruby Exits

Appending in Javascript

Sendgrid Ban

Clean URLs

Integer Division

Multi-tab Websockets

Bad Content

JS Data Chutzpah

Responsive tables

Concerns

Cookies

Emoji Bits

Git

Ruby vs. Ruby

Extending Devise

Rails UJS

ENV Variables

See All

Ruby Integer Division

April 2022

Let’s say we need to find the average of some values using Ruby.

Simple enough:

def find_avg(list)
	list.sum / list.size
end

find_avg([5,6,8,4])
> 5

Uhh, this doesn’t seem to be working.

We expected 5.75 but we returned 5.

There seems to be some sort of rounding (down) happening here.

Why would it be rounding down?

It turns out Ruby isn’t actually rounding down - it is automatically converting the result to an integer.

5.75.to_i
> 5

And running the to_i method appears to round down, but it actually contains no logic relating to rounding.

It is a blunt instrument that simply truncates all the stuff after the decimal, which seems similar to .floor, but has subtle differences.

Why is it converting the result to an integer?

When performing operations, Ruby will maintain type consistency with the result:

(3 + 4).class
> Integer

(3.4 + 4.1).class
> Float

("string" + "another string").class
> String

In some cases, Ruby will be able to work with mixed types:

(3 + 4.0).class
> Float

But Ruby cannot handle all mixes:

(3 + "string").class
> String cannot be coerced into Integer (TypeError)

Getting back to our earlier issue with the average — both the sum and the count were integers.

And Ruby wants the division of two integers to also be an integer.

And this presents us with some problems.

1 / 2
> 0

999 / 1000
> 0

# our initial example
23 / 4
> 5

This seems bad…?

My initial reaction as well … it seems integer division should return floats, as it feels counterintuitive for (1 / 2) == 0, even if there’s a reason for it (maintaining similar types).

After digging around a bit, it seems that there have been some proposals to ‘fix’ this issue in newer versions of Ruby, but attempts so far have been rejected, by none other than the creator of Ruby, Matz.

In 2018, in regards to this proposal, Matz had the final word.

Too big incompatibility. Abandoned.

Matz.

I believe the issue stems from backwards compatibility — think of all the code already written that expects integers when dividing, that could potentially break if this issue was resolved.

That said, Python had this same issue, but ‘fixed’ it with version 3.

Can we force a float quotient?

Yep, it’s fairly simple. Just convert one of your values to a float, and the result will be a float.

1 / 2
> 0

1.to_f / 2
> 0.5

1 / 2.to_f
> 0.5

It’s a bit clunky, but it does the trick.

Another option is to use the Ruby method fdiv:

1.fdiv 2
> 0.5

Last example

The examples above show that we’re clearly working with integers.

But, we don’t necessarily know the type of data we’re working with.

Let’s say we’re creating some sort of Venmo-like app where people can split a bill with friends. We write a simple function split_bill:

# Split bill

def split_bill(total, count)
	total / count
end

Somewhere else in the code, we bill some people based on a split:

total = 5
friend_ids = [4322, 1234, 5674, 1232, 5342, 9048]
per_friend = split_bill(total, friend_ids.count)

def bill(user_id, amount)
	# charge user amount
end

friend_ids.each do |f|
	bill(f, per_friend)
end

How much will each friend get billed?

0

Since the total was 5 (an integer), and the count was 6 (also an integer), our split method returned 5 / 6, which is 0.

We didn’t anticipate total would be an integer, but it was, and our app is broken.

So, as mentioned earlier, to prevent these type of errors, our split function should look like:

def split_bill(total, count)
	total.to_f / count
end

That’s all for now.