13

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?

mscha
  • 6,509
  • 3
  • 24
  • 40

1 Answers1

15

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 :(

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • 2
    Thanks, @Elizabeth! Simply adding `$*RAT-OVERFLOW = FatRat;` to the top of the script did the trick. (Edit: didn't see your edit yet; `INIT $*RAT-OVERFLOW = FatRat;` is probably a bit safer.) – mscha Apr 06 '22 at 10:26
  • 2
    Recycled this answer to a doc entry: https://github.com/Raku/doc/commit/0a7a2e5215 – Elizabeth Mattijsen Apr 06 '22 at 10:51
  • 2
    Perhaps `$*RAT-OVERFLOW` should also be mentioned on https://docs.raku.org/type/Rat? Somewhere around this sentence: “To prevent the numerator and denominator from becoming pathologically large, the denominator is limited to 64 bit storage. On overflow of the denominator a Num (floating-point number) is returned instead.” – mscha Apr 06 '22 at 11:01
  • 2
    See https://github.com/Raku/doc/commit/b95cdedbf8 . mscha++ – Elizabeth Mattijsen Apr 06 '22 at 11:46
  • 3
    FWIW I like the default behaviour. As [this](https://lemire.me/blog/2020/03/15/number-of-atoms-in-the-universe-versus-floating-point-values) states: It is estimated that there are about ```10**80``` atoms in the universe. Yet the maximal value that we can represent using the common double-precision floating-point type is larger than ```10**308```. It is an unimaginably large number. If your software ever produces a number so large that it will not fit in a double-precision floating-point value, chances are good that you have a bug. – librasteve Apr 06 '22 at 20:33
  • 2
    or you are calculating digits of π ;-) – librasteve Apr 06 '22 at 20:42