šŸ”¬39. Experimenting with Rats and Nums in Perl 6

šŸ”¬39. Experimenting with Rats and Nums in Raku

N. B. Perl 6 has been renamed to Raku. Click to read more.


Today, it will be a post with some small things about the two data types, Rat and Num.

First of all, Zoffix Znet added some insights on Twitter regarding my previous posts. Let me just quote them here.

Some useful information about DIVIDE_NUMBERS and DON'T_DIVIDE_NUMBERS:

FWIW, these will be gone some time these year, together with .REDUCE-ME method. Rats are meant to be immutable, so once we get native uint64s straightened out, to counteract perf loss from removal of DON’T_DIVIDE optimization, all of these ops will just be making new Rationals.

And about dividing by zero:

You can divide by zero *and* announce it to others, as long as you use the Num view of Rationals, which uses IEEE 754-2008 semantics with regards to division by zero
<Zoffix> m: .Num.say for 1/0, -1/0, 0/0
<camelia> rakudo-moar a9a9e1c97: OUTPUT: Ā«Infā¤-Infā¤NaNā¤Ā»

Let us play with dividing by 0 a bit more.

So, indeed, you can getĀ Inf if you cast a Rat value to Num:

$ perl6 -e'say (1/0).Num'
Inf

By the way, donā€™t forget that some spaces are meaningful in Perl 6. The following two lines of code are different:

say (1/0).Num;
say(1/0).Num;

The first line printsĀ Inf, while the second throws an exception. This is because the first line is equivalent to:

say((1/0).Num);

While the second line tries to convert the result of callingĀ say to Num.

Let us trace the data types in the following program:

my $x = 1/0;
say $x.WHAT; # (Rat)

# say $x; # Error

my $y = $x.Num;
say $y.WHAT; # (Num)

say $y;      # Inf

Is it possible that Rats also returnĀ InfĀ after division by zero?

First of all, here is the method of the Rational role that is used to convert a Rat number to a Num value:

method Num() {
    nqp::p6box_n(nqp::div_In(
       nqp::decont($!numerator),
       nqp::decont($!denominator)))
}

The rest of the work is thus done by some NQP code, which in the end gives usĀ Inf.

Let us start with a simple thing first and printĀ InfĀ when the value is stringified. Replace theĀ Str method of the Rational role with the following:

multi method Str(::?CLASS:D:) {
    unless $!denominator {
        return 'NaN' unless $!numerator;
        return 'Inf' if $!numerator >= 0;
        return '-Inf';
    }
}

This should only solve the problem in the cases when a ā€˜brokenā€™ number is used as a string, for example:

my $x = 1/0;
say $x; # Inf

my $y = -1/0;
say $y; # -Inf

my $z = 0/0;
say $z; # NaN

Surprisingly, it gave us even more, and we can use such numbers in calculations:

$ ./perl6 -e'my $x = 1/0; my $y = 1 + $x; say $y'
Inf

Now, look at the originalĀ StrĀ method:

multi method Str(::?CLASS:D:) {
    my $whole = self.abs.floor;
    my $fract = self.abs - $whole;

    # fight floating point noise issues RT#126016
    if $fract.Num == 1e0 { ++$whole; $fract = 0 }

    my $result = nqp::if(
        nqp::islt_I($!numerator, 0), '-', ''
    ) ~ $whole;

    if $fract {
        my $precision = $!denominator < 100_000
        ?? 6 !! $!denominator.Str.chars + 1;

        my $fract-result = '';
        while $fract and $fract-result.chars < $precision {
            $fract *= 10;
            given $fract.floor {
                $fract-result ~= $_;
                $fract -= $_;
            }
        }
        ++$fract-result if 2*$fract >= 1; # round off fractional result

        $result ~= '.' ~ $fract-result;
    }
    $result
}

If you debug the code, you will soon discover that the exception happens in the first lines, when theĀ abs method is called on a number.

This method is defined in the Real role:

method abs() { self < 0 ?? -self !! self }

Let us redefine it for Rationals (ignore negative values for now):

method abs() {
    if $!denominator == 0 {
        Inf
    }
    else {
        $!numerator / $!denominator
    }
}

Now, the check happens in this method. Letā€™s try it:

$ ./perl6 -e'my $x = 1/2; say $x;'
0.5

$ ./perl6 -e'my $x = 1/0; say $x;'
Inf.NaNNaN

Almost what is needed. You may fix the output as an exercise or just runĀ git checkout srcĀ šŸ™‚

 

One thought on “šŸ”¬39. Experimenting with Rats and Nums in Perl 6”

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code