Ruby’s Standard Library is full of many Objects that ship with just about any Object Oriented programming language. We have String, Hash, Array, Integer, and Float. However, there are several more “niche” Objects that many code schools and programming books overlook. In this article, I’m going to show off one of Ruby’s lesser utilized Objects: the Rational Class. What are they and what can you use them to do?

Before we get into an example, let’s go back to your middle school algebra class. Do you remember all those fractions? 4/3. 1/2. 15/40. These are exactly what Rational’s represent: Rational Numbers! This may not seem like a big deal to represent numbers this way because most of these examples have easy Float or BigDecimal representations (0.5 and .375), but what about 4/3rds or 5/9ths? The decimal representation of these is an infinitely repeating decimal. There is no possible way to represent this with your every day Float or BigDecimal and not lose precision. Rational’s are exactly how we can represent any kind of ratio in Ruby without losing precision!

Fun with Rationals:

Rationals don’t lose precision! If you repeatedly add Floats to an Integer, Ruby’s Float quickly loses precision. However, if you repeatedly add Rationals together, Ruby does not lose precision at all. The Result of adding 0.1 together 11 times is 1.1. The example below is the equivalent of (1/10) * 11.

(irb)> 11.times.inject(0) {|t| t + 0.1 }
=> 1.0999999999999999
(irb)> 11.times.inject(0) {|t| t + Rational(1,10) }
=> (11/10)

Note: Yes you can use .round(1) on the first example and get 1.1. That said, for large computations or more precise examples, Ruby will deviate a lot more and even after rounding the result could be incorrect. On the other hand, an algorithm using Rational’s will be spot on and you won’t have to use .roundat all.

Did you know Ruby has special syntax for creating Rationals? Bet you’ve never seen this before!

(irb)> 5/9r
=> (5/9)

The Float equivalent means writing 5.0/9.0 which is a tad ugly. Why the extra r? Because 5/9 in Ruby returns 0. That’s the Integer 5 divided by the Integer 9, which is rounded down automatically to 0.

Rationals in Action: Calculating Parimutuel Odds

This article will not walk through how to calculate parimutuel odds, there are already plenty of articles about how to do this. What I will show is how Rationals can be used to calculate these odds using the Ruby programming language to do it!

A Typical Tote Board

What we see in the image above are the first 7 runners at a race track. Runner 1 pays 7:1, runner 2 pays 12:1, and Runner 3 pays 11:1. Interesting enough, Runner 7 pays out at 6:5! This number is strange, but that’s how the math works as Runner 7 has the biggest win pool at $35,208. 6:5 is really 1.2 - so its actually worse than 2:1 odds! So how do we use Rational’s to get that number, 6:5? I’ll show you.

⚠️⚠️⚠️WARNING MATH ALERT ⚠️⚠️⚠️

First we need to calculate the decimal odds on Runner 7. This is done by taking the total win pool, $96,464 minus the Vigorish (20%), and divide it by Runner 7’s Win Pool, $35,208. This gives us (96464×.80) ÷ 35208. The decimal odds of Runner 7 is 2.191865485. In parimutual wagering formats, we then convert this decimal into the “fractional” representation by subtracting 1 and converting the resulting number into a rounded fraction. Fortunately, Ruby provides a .rationalize method that makes this pretty straight forward!

(irb)> (2.191865485 - 1).rationalize(0.01)
=> (6/5)

Sweet! We got 6-5!

What is the argument on rationalize? This is how we tell Ruby what level of precision to use when converting the precise decimal odds into a fraction. Without an argument, we would get the following:

(irb)> (2.191865485 - 1).rationalize
=> (119880890/100582567)

As you can see, in the first example we have successfully calculated what is on the tote board: 6 to 5. The same code can be used to calculate the odds of the other runners, too! What argument gets passed to rationalize needs to change when you are dealing with long shots (50:1), but the specific value depends entirely on the bookmaker for that specific race or track.

As I’ve demonstrated, its completely possible to use Ruby to calculate parimutuel odds! Without Ruby’s Rational class and the rationalize method, this would have been a lot more difficult to code!

Thanks Ruby 😍️

Are you doing anything interesting with Rationals? Let us know in the comments below!