Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rats from calculations and from Rat.new have different limits #454

Open
doomvox opened this issue Nov 28, 2024 · 3 comments
Open

Rats from calculations and from Rat.new have different limits #454

doomvox opened this issue Nov 28, 2024 · 3 comments
Labels
language Changes to the Raku Programming Language

Comments

@doomvox
Copy link

doomvox commented Nov 28, 2024

At present, Rats created from calculations are limited to 1 / (2⁶⁴ - 1),
and yet a Rat created via Rat.new can exceed that precision.
This oddity seems very peculiar, and I suggest that we consider
adding bounds checking to Rat.new so that both approaches have
an identical limit.

The current behavior is documented, but the rationale for it isn't: my
presumption is that it's intended to improve efficiency by avoiding
doing a check that will be redundant in many cases.

The difficulty here is that it makes the definition of a Rat a
little fuzzy, which makes it difficult to talk about it clearly:
this issue is addressed in a few places in the "language/numerics" page,
but it requires a code example to make the behavior clear:

https://docs.raku.org/language/numerics

If you don't see what I mean, try describing what a Rat is informally,
perhaps speaking out loud. "Though actually, you can have Rats fatter
than normal Rats but which are not FatRats..."

(Thanks to Rob Ramsbottom for calling this to our attention at the
SF Perl Raku Study Group.)

@doomvox doomvox added the language Changes to the Raku Programming Language label Nov 28, 2024
@raiph
Copy link

raiph commented Nov 28, 2024

At present, Rats created from calculations are limited to 1 / (2⁶⁴ - 1)

The denominator of a Rat calculation is not necessarily limited to 1 / (2⁶⁴ - 1). Denominator overflow behavior is instead controlled by the dynamic variable $*RAT-OVERFLOW. The default setting is to convert to a float (Num) on overflow.

yet a Rat created via Rat.new can exceed that precision.

I just peeked at Rakudo's Rat module and here's what I presume the code currently does, and then what I presume is/was intended.

The Rat class has no new method. So Rat.new calls the Rational[...] role's new method. And because it's (currently) Rat does ... Rational[Int, Int], the relevant new method creates an arbitrary precision rational number (like a FatRat, except the instance's class name presumably remains Rat).

There's a comment above the class Rat ... does Rational[Int, Int] declaration saying "should be Rational[Int, uint]". If it was changed to that then the Rat.new behavior would in principle be as you suggest. But in practice there were bugs with uint back in the day. So a comment was committed 7 years ago anticipating someone fixing uint. I recall Stefan and timotimo and/or Masterduke getting uint up to grade over the last couple years.

Conclusion?

It seems likely a good candidate for resolving this would be a PR that did s/Int/uint as appropriate plus whatever work a "boast" (my name for a cross between a toast and a blin) turns up until CI goes green.

I suggest that we consider adding bounds checking to Rat.new so that both approaches have an identical limit.

Based on my foregoing analysis I currently presume that both approaches can (and presumably should) have the same behavior, namely following the setting of the dynamic value of $*RAT-OVERFLOW.

And further, I presume everything will all fall into place if the s/Int/uint/ is successful.

The current behavior is documented, but the rationale for it isn't: my presumption is that it's intended to improve efficiency by avoiding doing a check that will be redundant in many cases.

Oh my! That sounded like an extraordinarily weak justification when I first read this issue, and I strongly doubted it was anything to do with something like that. Surely Raku(do) isn't that unprincipled! 😱

Anyhow, I then did research and drew the conclusions I've given above which suggest it was just waiting 7 years for us to get to it, which Stefan et al did (and spent about a year elapsed time getting right iirc). Raku: patient, principled, practical, pragmatic, persistent, perfectionist.


As part of preparing this comment I checked rational literal behavior and was surprised at its (apparent lack of) interactions with $*RAT-OVERFLOW (in a 2024.01 Rakudo). I'm leaving a mention of it here in the hope we recheck rational literal behavior if/when s/Int/uint/ happens and/or add some tests to roast and/or no one can reproduce the oddities I thought I observed. Like:

$*RAT-OVERFLOW = FatRat;
say 1 / 2⁶⁴;                   # 5.421010862427522e-20
say $*RAKU.compiler.version;   # v2024.01

@doomvox
Copy link
Author

doomvox commented Dec 2, 2024

Yes, I should've said something like "At present, Rats created from calculations are normally limited to 1 / (2⁶⁴ - 1)" to make it clear I was referring to the default overflow behavior. But then, if you've set "$*RAT-OVERFLOW = FatRat", then you wouldn't expect to be creating a Rat...

But as raiph points out, setting the overflow behavior doesn't always do what you'd expect. Sometimes it does, though... consider:

my $*RAT-OVERFLOW = FatRat; 

{
    my $n;

    $n = 1 / (2⁶⁴-1);                  
    say $n;          # 0.000000000000000000054
    say $n.WHAT;     # (Rat)

    $n = 1 / 2⁶⁴;                  
    say $n;          # 5.421010862427522e-20
    say $n.WHAT;     # (Num)

    $n = 1 / (2⁶⁴+1);                  
    say $n;          # 5.421010862427522e-20
    say $n.WHAT;     # (Num)
}

{
    for ( 2⁶⁴-1 .. 2⁶⁴+1 ) -> $d {
        my $n = 1 / $d;
        say $n;          
        say $n.WHAT;     
    }
    # 0.000000000000000000054
    # (Rat)
    # 0.00000000000000000005421011
    # (FatRat)
    # 0.00000000000000000005421011
    # (FatRat)
}

@librasteve
Copy link

I like @raiph s post and it looks that Rat does ... Rational[Int, uint] would fix this concern - along with fixing up the docs.

I share @doomvox s concern about this:

my $*RAT-OVERFLOW = FatRat; 

{
    my $n; 

    $n = 1 / 2⁶⁴;          
    say $n;          # 5.421010862427522e-20
    say $n.WHAT;     # (Num)      <=== ***should be a FatRat like below***
}

{
    for ( 2⁶⁴-1 .. 2⁶⁴+1 ) -> $d {
        my $n = 1 / $d; 
        say $n;            
        say $n.WHAT;       
    }   
    # 0.000000000000000000054
    # (Rat)
    # 0.00000000000000000005421011
    # (FatRat)
    # 0.00000000000000000005421011
    # (FatRat)
}

This looks like a plain old bug to me - surely an Int/Int division should produce a FatRat when the denominator is >=2^^64. Looks like sometimes we check > and sometimes >= somewhere (ie int vs uint limit). I have retested in Rakudo™ v2024.10. v6.d and v6.e.PREVIEW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language Changes to the Raku Programming Language
Projects
None yet
Development

No branches or pull requests

3 participants