Is there a way to automatically use FatRats in Raku?

Viewed 131

One of the neat things of Raku is that it automatically uses rational numbers instead of floating point numbers, when appropriate (e.g. when dividing two integers). Unfortunately, once the denominator gets too large, floating point numbers are used anyway.

There is the FatRat type that doesn't do that, but as far as I can find, the only way to use those is to explicitly do so.

For instance, this script calculates digits of π:

#!/usr/bin/env raku

unit sub MAIN(Int $decimals = 1_000);

sub atan_repr(Int $n, Int :$decimals)
{
    my $x = $n;
    my $n2 = $n²;
    my $sign = 1;
    my $limit = 10**($decimals+2);
    my $result = FatRat.new(1, $x);
    for 3,5...* -> $i {
        $x ×= $n2;
        $sign ×= -1;
        $result += FatRat.new($sign, $i × $x);
        last if $x ≥ $limit;
    }

    return $result;
}

my $π = 4 × (4 × atan_repr(5, :$decimals) - atan_repr(239, :$decimals));
my $π-str = ~$π;      # Make sure we don't do string conversion over and over

print '3. ';
for 0,5...^$decimals -> $d {
    print "    # $d\n   " if $d && $d %% 50;
    print $π-str.substr(2+$d,5), ' ';
}
print "    # $decimals" if $decimals && $decimals %% 50;
say '';

It's pretty elegant, except for stuff like FatRat.new(1, $x). I'd much rather be able to use just 1/$x and declare somehow that FatRats should automatically be used instead of Rats. Perhaps something like use bigrat, similar to Perl's use bigint and use bignum?

Is there a way to do this that I haven't found?

1 Answers

Using Rats by itself is fine, until you run out of precision (which is rare). I think it was the 2022.02 release that introduced the $*RAT-OVERFLOW dynamic variable. It specifies the behaviour that should be executed when a Rat overflows its precision. By default, it is set to Num, meaning it will revert to using (lossy) floating point.

You can also set it to FatRat, which will cause automatic upgrade to FatRat as soon as a Rat runs out of precision. You can also specify Failure (to have it fail), Exception (to have it throw an exception) or CX::Warn (to have it warn on downgrading to Num).

How does that work? The $*RAT-OVERFLOW variable is supposed to be a class or instance, on which the UPGRADE-RAT method will be called when a Rat overflows. So you could introduce your own behaviour by creating a class with an UPGRADE-RAT method in it.

So to activate this globally to upgrade to FatRat, you'd do:

INIT $*RAT-OVERFLOW = FatRat;

To activate this only for a lexical scope:

my $*RAT-OVERFLOW = FatRat;

I see this still needs to be documented :(

Related