Day 14: Going to the Rats

As I hinted at back in the in the Day 1 post, Perl 6 has rational numbers. They are created in the most straightforward fashion, by dividing an integer with another integer. But it can be a bit hard to see that there is anything unusual about the result:

> say (3/7).WHAT
Rat()
> say 3/7
0.428571428571429

When you convert a Rat to a Str (for example, to “say” it), it converts to a decimal representation. This is based on the principle of least surprise: people generally expect 1/4 to equal 0.25. But the precision of the Rat is exact, rather than the approximation you’d get from a floating point number like a Num:

> say (3/7).Num + (2/7).Num + (2/7).Num - 1;
-1.11022302462516e-16
> say 3/7 + 2/7 + 2/7 - 1
0

The most straightforward way to see what is going on inside the Rat is to use the .perl method. .perl is a standard Perl 6 method which returns a human-readable string which, when eval’d, recreates the original object as closely as is possible:

> say (3/7).perl
3/7

You can also pick at the components of the Rat:

> say (3/7).numerator
3
> say (3/7).denominator
7
> say (3/7).nude.perl
[3, 7]

All the standard numeric operators and operations work on Rats. The basic arithmetic operators will generate a result which is also a Rat if that is possible; the rest will generate Nums:

> my $a = 1/60000 + 1/60000; say $a.WHAT; say $a; say $a.perl
Rat()
3.33333333333333e-05
1/30000
> my $a = 1/60000 + 1/60001; say $a.WHAT; say $a; say $a.perl
Num()
3.33330555601851e-05
3.33330555601851e-05
> my $a = cos(1/60000); say $a.WHAT; say $a; say $a.perl
Num()
0.999999999861111
0.999999999861111

(Note that the 1/60000 + 1/60000 didn’t work in the last official release of Rakudo, but is fixed in the Rakudo github repository.)

There also is a nifty method on Num which creates a Rat within a given tolerance of the Num (default is 1e-6):

> say 3.14.Rat.perl
157/50
> say pi.Rat.perl
355/113
> say pi.Rat(1e-10).perl
312689/99532

One interesting development which has not made it into the main Rakudo build yet is decimal numbers in the source are now spec’d to be Rats. Luckily this is implemented in the ng branch, so it is possible to demo how it will work once it is in mainstream Rakudo:

> say 1.75.WHAT
Rat()
> say 1.75.perl
7/4
> say 1.752.perl
219/125

One last thing: in Rakudo, the Rat class is entirely implemented in Perl 6. The source code is thus a pretty good example of how to implement a numeric class in Perl 6.

8 thoughts on “Day 14: Going to the Rats

  1. It wasn’t immediately clear to me why “1 / 60000 + 1 / 60001” shouldn’t also be a rational. Is it that Rats are built on Ints, and 60000 * 60001 doesn’t fit in an Int? If so, then that makes sense. “Int” doesn’t appear anywhere in the post, though.

    1. It’s kind of wonky, which is why I think I skirted around the answer. Most directly, it’s because (in current Rakudo), 60000 * 60001 is a Num, not an Int, because it trips an Int overflow check after the multiplication. What’s weird about this is it trips that 32-bit overflow flag even if Rakudo is compiled with 64-bit Ints and thus is perfectly capable of storing 60000 * 60001 in an Int. (On my 32-bit OS X, (60000 * 60001).Int == -2147483648; on my 64-bit Linux machine, (60000 * 60001).Int == 3600060000. On both of them (60000 * 60001).WHAT is Num.)

      In theory, an Int should be a BigNum and able to handle 60000 * 60001 with ease, but that’s not the case in Rakudo. But then, in theory, the denominator of a Rat should be an Int64, a 64-bit Int, but Rakudo doesn’t have such a type yet. Once these types are in place, the gist of the example still is correct, but you’ll need larger numbers that 60001 to see the effect in practice.)

      1. Given that automatic bignums are in the spec, shouldn’t the 1/60000+1/60001 example always work, even for huge numbers? I.e., shouldn’t (1/$x+1/$y).WHAT always be Rat, regardless of the size of $x and $y?

      2. In the spec, Rats are Int over uint64 (that is, a unlimited size big num integer over an unsigned 64-bit int), so these issues do apply. If you need unlimited precision regardless of efficiency, the spec provides the FatRat type, which is an Int over an another Int. There’s been no attempt to implement this, as Rakudo does not have bignums yet.

  2. That was great — probably one of the most fun (for me) articles so far. I have no darn idea what I’d ever use them for, but the overall interface and simplicity of use seems, from this post, really nice.

Leave a reply to Ricardo Signes Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.