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.
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.
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.)
Will there be automatic big numbers in Rakudo in future releases? That’s a feature I was always impressed by in Common Lisp.
They are in the spec, but probably won’t make it into Rakudo until the second half of 2010.
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?
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.
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.
In fact I had a use case right away when they were implemented: In a chart plotting library floating point errors would mean that the Zero-tick would actually be 1e-17 or so.
Instead of special-casing the zero, I changed a few lines in my code and had rats working, and “magically” the 1e-17 became 0.
See here for an illustration: http://perlgeek.de/blog-en/perl-6/rats-and-other-pets.html