Yesterday, we were digging into Rakudo Perl 6 to understand when a Rat value becomes a Num value. It turned out that if the value becomes too small, which means its denominator gets bigger and bigger, Rakudo starts using a Num value instead of Rat.

We found the place where it happened. Today, let us make an exercise and see if it is possible that Perl 6 behaves differently, namely, it expands the data type instead of switching it to a floating point and losing accuracy.

The change is simple. All you need is to update the `if`s inside theÂ `DIVIDE_N` routine:

--- a/src/core/Rat.pm +++ b/src/core/Rat.pm @@ -48,16 +48,14 @@ sub DIVIDE_NUMBERS(Int:D \nu, Int:D \de, \t1, \t2) { ($numerator := -$numerator), ($denominator := -$denominator))), nqp::if( - nqp::istype(t1, FatRat) || nqp::istype(t2, FatRat),+ nqp::istype(t1, FatRat) || nqp::istype(t2, FatRat) || $denominator >= UINT64_UPPER,nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(FatRat),FatRat,'$!numerator',$numerator), FatRat,'$!denominator',$denominator), - nqp::if( - $denominator < UINT64_UPPER, nqp::p6bindattrinvres( nqp::p6bindattrinvres(nqp::create(Rat),Rat,'$!numerator',$numerator), - Rat,'$!denominator',$denominator), - nqp::p6box_n(nqp::div_In($numerator, $denominator)))))+ Rat,'$!denominator',$denominator) + ))}

Now, there are two outcomes: either the routine generates a Rat value or a FatRat. The latter happens when the sub arguments were already FatRats or when the current Rat gets too close to zero.

Compile and test our modifiedÂ `perl6` executable with Newtonâ€™s algorithm from yesterdayâ€™s post:

my $N = 25; my @x = Rat.new(1, 1), -> $x { $x - ($x ** 2 - $N) / (2 * $x) } ... *; .WHAT.say for @x[0..10]; .say for @x[1..10];

As expected, the first elements of the sequence are Rats, while the tail is made of FatRats:

(Rat) (Rat) (Rat) (Rat) (Rat) (Rat) (FatRat) (FatRat) (FatRat) (FatRat) (FatRat)

Also, you can easily see it if you print the values:

13 7.461538 5.406027 5.01524760 5.0000231782539490 5.0000000000537228965718724535111 5.00000000000000000000028861496160410945540567902983713732806515 5.000000000000000000000000000000000000000000008329859606174157518822601061625174583303232554885171687075417887439374231515823 5.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000693865610585718905982734693675309615913812411108046914931948226816763601320201386971350204028084660605790650314446568089428143916887535905115787146371799888 5.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004814494855534925123195523522159753005055993378092336823010386671077751892080269126953923957066141452855241262256569975702944214065988292758274535222239622977104185030432093986146346015004230914044314506580063758070896734658461687838556535528402765772220596451598003813021305355635793333485373058987453787504731

## * * *

I donâ€™t know what is better â€” to have two different types for a rational number (not counting the Rational role) or one type that can hold both â€˜narrowâ€™ and â€˜wideâ€™ values, or a mechanism that switches to a wider data type when there is not enough capacity. I feel the best is the last option (in the case that FatRat and Rat are using different types for storing numerators and denominators, of course).

As far as I understand, that was exactly the original thought:

*For values that do not already do theÂ NumericÂ role, the narrowest appropriate type ofÂ Int,Â Rat,Â Num, orÂ ComplexÂ will be returned; however, string containing two integers separated by aÂ /will be returned as aÂ RatÂ (or aÂ FatRatÂ if the denominator overflows anÂ int64).*

Also it feels more natural to silently add more space for more digits instead of breaking the idea of having the Rat type. Anyway, there are different opinions on this, but that should not stop Perl 6 from being widespread.

## One thought on “ðŸ”¬56. A bit more on Rat vs FatRat in Perl 6”