No such thing as a small change PerlMonks

### [Raku] Arcane treatment of doubles and rationals

by syphilis (Bishop)
 on Apr 03, 2021 at 11:38 UTC Need Help??

syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I ran the following script with raku:
```my \$d_1 = 0.1e0; # double   0.1 ( == 3602879701896397/3602879701896396
+8 )
my \$r_1 = 0.1;   # rational 1/10

my \$d_2 = 0.10000000000000001e0;# double, same value as \$d_1
my \$r_2 = 0.10000000000000001;  # rational 10000000000000001/100000000
+000000000

# check that \$d_1 == 3602879701896397/36028797018963968
say "not ok 0" if \$d_1 != 3602879701896397/36028797018963968;

# Check that \$d_1 != 1 / 10;
say "not ok 1" if \$d_1 == 1 / 10;

# Check that \$d_1 and \$d_2 are assigned to exactly the same value:
say "not ok 2" if \$d_1 != \$d_2;

# Check that \$r_ and \$r_2 are assigned different values:
say "not ok 3" if \$r_1 == \$r_2;

# Since \$r_1 and \$r_2 are unequal, \$d_1 should not be equal to both \$r
+_1 && \$r_2.
# We check this, interchanging lhs and rhs operands in case that makes
+ a
# difference:
say "not ok 4" if (\$d_1 == \$r_1 && \$d_1 == \$r_2);
say "not ok 5" if (\$r_1 == \$d_1 && \$r_2 == \$d_1);

# Similarly \$d_2 should not be equal to both \$r_1 and \$r_2:
say "not ok 6" if (\$d_2 == \$r_1 && \$d_2 == \$r_2);
say "not ok 7" if (\$r_1 == \$d_2 && \$r_2 == \$d_2);

say Rat(\$d_1);
say Rat(\$d_2);
I was surprised that it output:
```not ok 1
not ok 4
not ok 5
not ok 6
not ok 7
0.1
0.1
However, I think I understand what's happening.
I had thought that when raku compared a double with a rational, it would convert the double to its exact rational value, and then compare the 2 rationals.
Instead it apparently converts the rational to a double (rounding if necessary), and then compares the 2 doubles.

I can see some sense in performing the comparison in the way they've chosen:
1) It's probably much cheaper to convert a rational to a double than vice-versa;
2) If the rounded rational is equivalent to the double then, under certain circumstances, you might be happy enough to think of the 2 values as being equivalent.

Doing it that way might be a useful comparison to be making, but I don't think it's a comparison that the numeric comparison operators (==, <, >, etc.) should be performing.

Anyway - the first of my questions (and there probably will be more):
Is there a way to get raku to explicitly display the fact that the (double precision) values held by \$d_1 and \$d_2 are exactly equivalent to the rational value 3602879701896397/36028797018963968 ?

Cheers,
Rob

Replies are listed 'Best First'.
Re: [Raku] Arcane treatment of doubles and rationals
by holli (Abbot) on Apr 03, 2021 at 22:41 UTC
display the fact that the (double precision) values held by \$d_1 and \$d_2 are exactly equivalent to the rational value 3602879701896397/36028797018963968
I think your assumption is wrong.
```\$ say 3602879701896397/36028797018963968 == 0.100000000000000006;
True
\$ say 10000000000000001/100000000000000000 == 0.10000000000000001 == 0
+.10000000000000001e0 == 0.1 + 1e-18;
True

== and the other operators don't care about the type of the number they work with, they will coerce every operand to Num anyway. However
```\$ say 3602879701896397/36028797018963968 =~= 0.10000000000000001;
True
There is also a unicode variant of the approximately-equal operator but it won't display correctly here.

holli

You can lead your users to water, but alas, you cannot drown them.
\$ say 3602879701896397/36028797018963968 == 0.100000000000000006;
True

Have they changed the spec ?
```> say 3602879701896397/36028797018963968 == 0.100000000000000006;
False
I have:
```\$ raku -v
Welcome to Rakudo(tm) v2021.03.
Implementing the Raku(tm) programming language v6.d.
Built on MoarVM version 2021.03.
If the RHS is a rational, then it's fairly easy to show that 3602879701896397/36028797018963968 != 100000000000000006/1000000000000000000.
(A/B == C/D if and only if A*D == B*C. In this case, A*D ends in zero and B*C ends in 8 so the equivalence cannot possibly hold.)

If the RHS is a double, then it's correct that 3602879701896397/36028797018963968 == 0.100000000000000006.
However, the 3 doubles assigned (respectively) the 3 values 0.100000000000000006, 0.10000000000000001 and 0.1 are all exactly the same. So we find, as expected:
```> say 3602879701896397/36028797018963968 == 0.100000000000000006e0;
True
> say 3602879701896397/36028797018963968 == 0.10000000000000001e0;
True
> say 3602879701896397/36028797018963968 == 0.1e0;
True
I've long been curious about the mindset that has created the rational/double arithmetic on raku, and I had thought (hoped) it might be a simple task to see how it all fits together.
Alas no, and I'm starting to see that I'm going to have to locate and work through the relevant documentation if I ever want to understand it.

Cheers,
Rob
There is a loss of precision happening when using the exponential form.
```\$ raku -e "dd 0.10000000000000006e0"
0.10000000000000006e0
\$ raku -e "dd 0.100000000000000006e0"
0.1e0
The exponential form creates a Num which needs to be between -1.7976931348623157e308 to 1.7976931348623158e308. See what Raku considers Infinity.

holli

You can lead your users to water, but alas, you cannot drown them.
Re: [Raku] Arcane treatment of doubles and rationals
by syphilis (Bishop) on Apr 04, 2021 at 13:04 UTC
I had thought that when raku compared a double with a rational, it would convert the double to its exact rational value, and then compare the 2 rationals.
Instead it apparently converts the rational to a double (rounding if necessary), and then compares the 2 doubles.

I've managed to satisfy myself that this is, in fact, what is going on. And based on this newfound wisdom, I've modified (and expanded) the original raku script that I provided so that it outputs no "not ok..." messages at all:
```
my \$d_1 = 0.1e0; # double   0.1 ( == 3602879701896397/3602879701896396
+8 )
my \$r_1 = 0.1;   # rational 1/10

my \$d_2 = 0.10000000000000001e0;# double, same value as \$d_1
my \$r_2 = 0.10000000000000001;  # rational 10000000000000001/100000000
+000000000

# check that \$d_1 == 3602879701896397/36028797018963968
say "not ok 0" if \$d_1 != 3602879701896397/36028797018963968;

# Check that \$d_1 == 1 / 10
# This should be true because the RHS will be rounded to 0.1e0
say "not ok 1" if \$d_1 != 1 / 10;

# Check that \$d_1 and \$d_2 are assigned to exactly the same value:
say "not ok 2" if \$d_1 != \$d_2;

# Check that \$r_ and \$r_2 are assigned different values:
say "not ok 3" if \$r_1 == \$r_2;

# Although \$r_1 and \$r_2 are unequal, \$d_1 should be equal to both \$r_
+1 && \$r_2.
# This is because both \$r_1 and \$r_2 round to 0.1e0
# We check this, interchanging LHS and RHS operands in case that makes
+ a
# difference:
say "not ok 4" if (\$d_1 != \$r_1 && \$d_1 != \$r_2);
say "not ok 5" if (\$r_1 != \$d_1 && \$r_2 != \$d_1);

# Similarly \$d_2 should be equal to both \$r_1 and \$r_2:
say "not ok 6" if (\$d_2 != \$r_1 && \$d_2 != \$r_2);
say "not ok 7" if (\$r_1 != \$d_2 && \$r_2 != \$d_2);

# The following 9 rationals should all round to 0.1e0.
# They equate to the 56-bit values:
# 0.11001100110011001100110011001100110011001100110011001100E-3
# 0.11001100110011001100110011001100110011001100110011001101E-3
# 0.11001100110011001100110011001100110011001100110011001110E-3
# 0.11001100110011001100110011001100110011001100110011001111E-3
# 0.11001100110011001100110011001100110011001100110011010000E-3
# 0.11001100110011001100110011001100110011001100110011010001E-3
# 0.11001100110011001100110011001100110011001100110011010010E-3
# 0.11001100110011001100110011001100110011001100110011010011E-3
# 0.11001100110011001100110011001100110011001100110011010100E-3

say "not ok 8"  if 14411518807585587/144115188075855872 != 0.1e0;
say "not ok 9"  if 57646075230342349/576460752303423488 != 0.1e0;
say "not ok 10" if 28823037615171175/288230376151711744 != 0.1e0;
say "not ok 11" if 57646075230342351/576460752303423488 != 0.1e0;
say "not ok 12" if 3602879701896397/36028797018963968   != 0.1e0;
say "not ok 13" if 57646075230342353/576460752303423488 != 0.1e0;
say "not ok 14" if 28823037615171177/288230376151711744 != 0.1e0;
say "not ok 15" if 57646075230342355/576460752303423488 != 0.1e0;
say "not ok 16" if 14411518807585589/144115188075855872 != 0.1e0;

# The following 2 rationals should not round to 0.1e0.
# They equate to the 56-bit values:
# 0.11001100110011001100110011001100110011001100110011001011E-3 (less
+than 0.1 by 1ULP)
# 0.11001100110011001100110011001100110011001100110011010101E-3 (great
+er than 0.1 by 1ULP)

say "not ok 17" if 57646075230342347/576460752303423488 == 0.1e0;
say "not ok 18" if 57646075230342357/576460752303423488 == 0.1e0;

# Each of the 11 rationals just provided should be unequal to each oth
+er.
# We check this for a few (selected) values of these rationals.

say "not ok 19" if 14411518807585587/144115188075855872 == 57646075230
+342349/576460752303423488;
say "not ok 20" if 14411518807585587/144115188075855872 == 14411518807
+585589/144115188075855872;

say "not ok 21" if 14411518807585587/144115188075855872 == 57646075230
+342347/576460752303423488;
say "not ok 22" if 14411518807585589/144115188075855872 == 57646075230
+342357/576460752303423488;
And I think I understand the reasons (which are specified in the comments) that this particular script produces no output at all.
I don't agree with some of those reasons, though I don't rule out the possibility that the case is, at least, arguable.
And the documentation I found was full of all sorts of nomenclature/notation that I didn't understand.

There are, however, some additional aspects that I believe are nothing other than "weird buggery" - and I'll eventually report these "additional aspects" to the appropriate forum unless someone here likes to show me the errors in my thinking.
For example:
```> say 838861/8388608
0.10000002
How is that in any way accurate, helpful or useful ?
Neither as a rational nor a double does 838861/8388608 == 0.10000002
```> say 838861/8388608 == 0.10000002
False
> say 838861/8388608 == 0.10000002e0
False
The first interesting thing about the rational 838861/8388608 is that if you assign the value 0.1 to a 20-bit precision floating point data type, then the value assigned is exactly equivalent to 838861/8388608.
The second interesting thing is that, if you assign the value 0.10000002e0 to a 20-bit precision floating point data type, then you assign exactly the same value (ie 838861/8388608).
But, AFAIK, raku does not provide us with a 20-bit precision floating point type ... so why does it present us with a 20-bit precision floating point value ?
(I don't believe it would intentionally do that ... which is the reason that I'm thinking "bug".)
For mine, it would make far better sense to provide us with a value that can actually be verified:
```> say 838861/8388608 == 0.10000002384185791e0
True
Even more tantalizing:
```> say 56294995342131/562949953421312
0.0999999999999996
> say 56294995342131/562949953421312 == 0.0999999999999996
False
> say 56294995342131/562949953421312 == 0.0999999999999996e0
False
```> say 56294995342131/562949953421312 == 0.09999999999999964e0
True
I have other similar examples.

Sorry - I probably should have placed my original post on this in the rants section.
I have no objections if someone wants to move this thread there.

Cheers,
Rob
```\$ raku -e "say (838861/8388608).raku"
0.10000002384185791015625

\$ raku -e "dd 838861/8388608"
0.10000002384185791015625

\$ raku -e "say (838861/8388608).gist"
0.10000002

\$ raku -e "say 838861/8388608"
0.10000002

If you use say It will call gist on the object to say, trying to provide something human (easily) readable. Use dd instead or call the raku method.

holli

You can lead your users to water, but alas, you cannot drown them.
Thanks - the raku and gist methods were what I needed to locate and comprehend.
```\$ raku -e 'my \$r = 3602879701896397/36028797018963968; say \$r.raku; sa
+y \$r.raku == \$r;'
0.1000000000000000055511151231257827021181583404541015625
True

This is much more like what I expected of the raku language.
I'm surprised that the gist method sacrifices accuracy so readily. Even 1/128 does not render the exact value, providing 0.007813 instead of 0.0078125.
But that's a matter of choice - I now see nothing buggy happening there.

As regards converting a double to its exact rational representation, this is quite simple in perl:
```\$ perl -le 'printf "%.800g\n", 0.1'
0.1000000000000000055511151231257827021181583404541015625
\$
Is the same thing also as straightforward in raku ?
I tried:
```\$ raku -e 'printf "%.800g\n", 0.1e0'
NaN
\$
I've read documentation stating that raku's printf() expects that the last argument must be a string.

Cheers,
Rob

Create A New User
Node Status?
node history
Node Type: perlquestion [id://11130768]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (2)
As of 2021-04-18 17:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?

No recent polls found

Notices?