http://www.perlmonks.org?node_id=1201566

Anonymous Monk brought up a really interesting discovery here. Unfortunately, that thread got derailed, so I’m making a separate one, as suggested by Your Mother. One of the first things I found while testing is this really interesting tidbit:
$ perl6 -e 'say 0.99999999999999999000001' 1.000000000000000073886090 $ perl6 -e 'say 0.99999999999999999000001 > 1' True
But then I realized I was using an outdated Rakudo (2017.04). So I updated to 2017.09, and now those print 1 and False, respectively. There’s still some interesting behavior in 2017.09, though:
$ perl6 -e 'say 0.7777777777777777777770' 0.77777777777777785672697 $ perl6 -e 'say 0.7777777777777777777771' 0.777777777777777767909129
Note that the second number printed is strictly smaller than the first one, even though the second source number is strictly larger than the first one, spelled in the same fashion and to the same number of significant digits! However, comparison and subtraction still return exact results:
$ perl6 -e 'say 0.7777777777777777777771 > 0.7777777777777777777770' True $ perl6 -e 'say 0.7777777777777777777771 - 0.7777777777777777777770' 1e-22
Okay, that’s probably because one is a Num and the other is a Rat, so let’s convert everything to Num explicitly:
$ perl6 -e 'say Num(0.7777777777777777777770)' + 0.777777777777778 $ perl6 -e 'say Num(0.7777777777777777777771)' 0.777777777777778 $ perl6 -e 'say Num(0.7777777777777777777770) > Num(0.7777777777777777 +777771)' True $ perl6 -e 'say Num(0.7777777777777777777770) - Num(0.7777777777777777 +777771)' 1.11022302462516e-16
Huh. Now they print the same, but they’re still different numbers when compared. Note that the sign of the difference got switched:
$ perl6 -e 'my $a = 0.7777777777777777777770; my $b = 0.77777777777777 +77777771; say $a <=> $b; say Num($a) <=> Num($b)' + Less More
Also interesting is that many Nums don’t survive a round-trip to Str:
$ perl6 -e 'my $a = Num(1/9); say $a == Num(Str($a))' False
Can anyone point me to the Perl6 specs/docs/whatever that explain those behaviors?

Replies are listed 'Best First'.
Re: Perl6 discoveries — floating-point
by syphilis (Archbishop) on Oct 18, 2017 at 12:24 UTC
    Update: Since posting, I've realised that I probably haven't accurately addressed the actual situation as there appears to be other weird stuff going on ... which will have to keep until another day.
    I had, for example, overlooked the oddness of Num(0.7777777777777777777770) > Num(0.7777777777777777777771) being true. As regards 53-bit (double) precision those 2 values should be equivalent.
    I get the same on rakudo-star-2017.07:
    C:\>perl6 -e "say Num(0.7777777777777777777770) > Num(0.77777777777777 +77777771);" True
    But then:
    C:\>perl6 -e "say Num(0.7777777777777777777770e0) > Num(0.777777777777 +7777777771e0);" False C:\>perl6 -e "say Num(0.7777777777777777777770e0) == Num(0.77777777777 +77777777771e0);" True
    So I probably need to better understand the rules that apply to Num().

    Huh. Now they print the same, but they’re still different numbers when compared

    When you say()/print() the number it gets rounded to 15 decimal digits - and it's quite common that different doubles (and therefore different values) will round to the same 15 decimal digit precision value.
    It's exactly the same situation as with perl5 (which also prints out values rounded to 15 decimal digits).
    If say()/print() rounded the doubles to 17 decimal digits of precision this type of discrepancy would be avoided.

    Also interesting is that many Nums don’t survive a round-trip to Str

    I think it's the same issue - to "survive" that "round-trip" there are many values that would require Str() to return 17 decimal digits of precision.

    This is the reason that many languages (eg Go, Haskell, Python3, Ruby, Swift) do output 17 decimal digits.
    It's also the reason that the mpfr C library outputs 17 decimal digits (by default) for 53-bit precision floating point values.

    Can anyone point me to the Perl6 specs/docs/whatever that explain this behavior?

    The explanation is found in the math.
    In order to guarantee surviving the round-trip experience, the number of decimal digits required is given by the expression 1+ceil(P*log(2)/log(10)), where P is the precision (in bits) of the given floating point value.
    This equates to 17, 21 and 36 decimal digits for (respectively) 53, 64 and 113 bit floating point values.

    I don't know of any documentation (for either perl5 or perl6) that explains the choice of 15 decimal digits.
    I haven't even seen any compelling justifications of that choice ... though I have seen some pretty lame excuses for it.

    Cheers,
    Rob
      Thanks for your answer.

      Python3, JavaScript, Ruby, Java (and probably others) don’t always output 17 decimal digits: they output as few digits as possible while still ensuring that parsing back the output yields the original number. This sounds like the smart thing to do.

      I just found out that Python2 makes a similar, but much worse, mistake: it rounds at 12 digits! It’s good to see that Python3 fixed this particular wart; too bad Perl6 kept the weird Perl5 behavior, here.

      I should’ve made it clearer that I was looking for explanations to all these things, not just the last one. What’s your take on the second-to-last example, where $a < $b, and yet Num($a) > Num($b)? (update: you updated your post while I was typing, and now it partly addresses this, yay!)

        This sounds like the smart thing to do

        It's my preferred option, but there's a view that 17 digits is just too much clutter and only gives one a level of precision that one neither generally wants nor can comprehend.
        And I think that's how choices of 12 or 15 might have come about.
        So ... having print() output a "thereabouts" value is not universally condemned - given that one can always see the extra decimal digits by doing printf "%.16e", $double

        However, whenever I've tried to get the full 17 decimal digits by using perl6's printf() the last 2 digits are always "0" :
        C:\>perl6 -e "printf '%.16e\n', 1.7320508075688772e0;" 1.7320508075688800e+00
        (The perl6 developers have been recently informed of this.)

        What’s your take on the second-to-last example, where $a < $b, and yet Num($a) > Num($b)?


        I speculate that there's a bug in the conversion of rational values to doubles:
        C:\>perl6 -e "say Rat(0.7777777777777777777770) > Rat(0.77777777777777 +77777771);" False C:\>perl6 -e "say Num(Rat(0.7777777777777777777770)) > Num(Rat(0.77777 +77777777777777771));" True
        It is known that the way that perl6 assigns doubles can result in weird discrepancies and, given that the 2 rationals are so close together in value, I would not be surprised if that method of assigning the doubles is the culprit.
        Note that if you do:
        C:\>perl6 -e "say Num(0.7777777777777777777770e0) == Num(0.77777777777 +77777777771e0);" True
        then you avoid the "Rat to Num" conversion and get the correct result.
        (The two values are deemed equal because, of course, they both equate to the same double.)

        And I believe that last one-liner can equivalently be written as:
        C:\>perl6 -e "say 0.7777777777777777777770e0 == 0.77777777777777777777 +71e0;" True

        Cheers,
        Rob
Re: Perl6 discoveries — floating-point
by Grimy (Pilgrim) on Oct 18, 2017 at 16:45 UTC
    More floating-point fun! .perl’s documentation states that its output “can usually be re-evaluated with EVAL to regenerate the object”, yet it uses the same imprecise representation as Str:
    $ perl6 -MMONKEY-SEE-NO-EVAL -e 'my $a = 1e0 + 1e-15; say EVAL($a.perl +) == $a' False
    Sets can be equal even if their elements aren’t:
    $ perl6 -e 'my ($a, $b) = (1e0, 1e0 + 1e-15); say $a == $b; say set($a +) ~~ set($b); say $a (-) $b' False True set()