When (ruby) floating just isn't good enough

Nov 28, 2008

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.

Hi, I'm Glenn! 👋 I've spent most of my career working with or at startups. I'm currently the Director of Product @ Ockam where I'm helping developers build applications and systems that are secure-by-design. It's time we started securely connecting apps, not networks.

Previously I led the Terraform product team @ HashiCorp, where we launched Terraform Cloud and set the stage for a successful IPO. Prior to that I was part of the Startup Team @ AWS, and earlier still an early employee @ Heroku. I've also invested in a couple of dozen early stage startups.