Glenn Gillen

When (ruby) floating just isn't good enough

Hopefully we all know that when talking about numbers "float" means "floating point" and as such the number isn't entirely precise, it's just an approximation. Most of the time it's a good enough approximation to serve our needs, but when it comes to dealing with money it's best to play it safe and have the absolute precision we want. Otherwise all those .000001 of a cents can add up to a large sum.

Working with fixed point arithmetic

To get around the short-comings of using floating points, a fairly common and trivial task is simply multiply it to the number of significant digits we care about and then treat it as an integer. So for dealing with money, we would multiply the value $120.19 by 100 to end up with 12019 cents. Any arithmetic we do, we do on the expanded cent value and we store that in the database. If we want to display a nicely format dollar value to the end user, we do that after we pull it back out from the database.

Losing money with Ruby

As I said, all this is rather trivial and many of you have probably had to do it in the past. So here is an excercise for the reader to follow along with at home. What do you think the following would output?

"0.29".to_f

If you answered 0.29 pat yourself on the back. Let's take it a step further:

"0.29".to_f * 100

Who answered 29.0? Congratulations. Okay, and now to take home the full showcase and all the cash:

("0.29".to_f * 100).to_i

Do we hear a 29 out there? Hooray! You're not alone, but you're wrong. Fire up your nearest ruby console and give it a shot, you'll actually get _28_ as the output. If you've made the same assumption yourself in the past, it'd be worth going back and checking your code to make sure it works. Likewise if you're using a 3rd party library it's worth double-checking they've done the correct implementation.

Doing ruby floating points and money properly

So how do you get around this problem? Well from reading the ruby docs on float I'd still expect .toi_ to work, but clearly it's not actually truncating the value as documented. Instead, you have to call round():

("0.29".to_f * 100).round

Alternatively, you can use the money gem as it does it properly.

Glenn Gillen

I'm an advisor to, and investor in, early-stage tech startups. Beyond that I'm an incredibly fortunate husband and father. Working on a developer-facing tool or service? Thinking about starting one? Email me and let me know or come to one of our days to help make it a reality.