Perl 6 small stuff #12: Cobol precision, revisited (or… even more fun with FatRats)

Let me start by saying that this will be the last article exploring peculiarities of Perl 6’s maths functions. I promise. At least for a while. With that said, let’s dive back into rational numbers vs floating point one last time.

Binary representation of the floating point number 0.15625. CC © Wikimedia Commons.

Back in number 4 of this series, I tried to figure out the precision of Perl 6‘s ’rational numbers compared to Cobol’s fixed precision types. That may sound like a strange thing to do, but I had just read an article about why Cobol still was preferred by financial institutions (read it if you haven’t already — this article will be easier to understand if you do, especially why the particular algorithm below is used to check precision). It had to do with how Cobol could control floating point precision to avoid rounding errors that accumulated over time could cost banks, stock exchanges, the IRS, etc., millions of dollars. The author proved that by comparing a little Python snippet to Cobol, a script that broke Python in all the expected ways.

Since Perl 6 use the Rat (rational) data type under the hood for lots of things, even without explicit typing on the programmer’s part, my hypothesis was that Perl 6 wouldn’t have these kinds of errors at all. So I made a Perl 6 implementation of the error provoking algorithm outlined in the Cobol article. And wouldn’t you know it: Perl 6 didn’t have the errors mentioned in the Cobol article. I proudly spread that conclusion in my Having fun with Rats post.

As it turns out I was a little too trigger happy. In my article I just tested the algorithm to 20 iterations since that was what the Cobol article did. When tinkering with the code afterwards I found that my Perl 6 code broke at around 28 iterations. So although it fared better than Python, and went beyond the Cobol example, even Perl 6 wasn’t foolproof.

That made me think: What if I implemented it using explicit typing, this time forcing the use of FatRats? So I did:

#!/usr/bin/env perl6
sub rec(FatRat $y, FatRat $z) {
return 108 — (815–1500/$z)/$y;
}
sub default-behavior(Int:D $N) {
my @x = FatRat.new(4), FatRat.new(17, 4);
for (2..$N+1) -> $i {
my $n = rec(@x[$i-1], @x[$i-2]);
@x.append($n);
}
return @x;
}
my @y = default-behavior(2000);
for @y.kv -> $i, $p {
say $i.fmt(“%02d”) ~ “ | “ ~ @y[$i].fmt(“%3.20f”);
say join “ / “, @y[$i].nude;
}

As you can see I ran the code up to 2000 iterations. And Perl 6 didn’t break once. I figure I could continue much longer, but that wouldn’t prove my point any more than it already had: FatRats seemingly handle everything.

At around 2000 iterations, the floating point representation was an even 5. The FatRat had increased to something beyond readable:

2001 | 5.00000000000000000000
10887262270271520844470244368473590369823879678381735770804366536872861066757834267230923438499734851331955108971818900795255377964985420406353488530653796076376888302887871648059636404237386779810073204677465433988288796371463779266725835301768375910295365966893549342070113935097170738554786589822697649361389182715711445880206813457196212233603705832810491425136584469585820510373307587755977621707298634317089590138855983741462833338843271340817765566670690233393031687431055903364260747271559744687940769179892188275670612186517021074968728089773868903232112413409395441640658761326225416663244033887317133246740410946847989349983326675880888985445567168787598735624717364724914686476958266124806775762813974242476406932409145206237566267025023985919713838560053160634848162125063714368558880445253361406701423828947352575925454715484530130867655804116616415778049891344295072286232982121196487495343247308360407488300676499377578310332816377975448193225536813649463486171661227928324492008794414474774496406886870868799498753785243478428165856592662574587026062708505894963955557204003803392615937919983143327086591424359681275817919140507987737463394218763462404759849484338109892177212883565977961986616331952406228280515381736751779687947623033277314097434771424807829329022829618524311375471611964574251871376643107540529697952215346315210658268262117062285956468458853775876425932095612817 / 2177452454054304168894048873694718073964775935676347154160873307374572213351566853446184687699946970266391021794363780159051075592997084081270697706130759215275377660577574329611927280847477355962014640935493086797657759274292755853345167060353675182059073193378709868414022787019434147710957317964539529872277836543142289176041362691439242446720741166562098285027316893917164102074661517551195524341459726863417918027771196748292566667768654269212275864367729012474591108985007522990289641915425059624720960922390197391499416012195286133802412263320144198333588435944700149853436940773893604588831895901210854703334389818287374956988979446767663405991767869404053875825243861633344428650812796466524645611445569036116412001976972688038699652154919003410804565289198476051171009210210514432258823939333189758824739429056132928366378633982114880626729252784764043010629666202312909247751673764726461337254048195958324091876383541819502316785741383639145866071856825051549231447161710399611381911575660385601546360041084280896676911889810584537683240864405819307049463732925606631546639518426651121208176285007436520251461854127484076350233519219480398658641001242539048046529742114192549782403287806556840860371337966097173350218236381329518702831401361914811597448759832549227759610938548970537888455950617834972888158093407922606309451101643311842471881838667105291086022959240750179576618885386564

I can’t say that I’m anything but impressed. So go on. Reimplement your bank systems in Perl 6 🤪.


Since these articles are nothing without a comparison to something else, I figured I’d do it again. Since I had so much success using Perl 5 with the use bignum pragma in my last article (Numberphile calculator test), I decided, just for fun, to implement this in Perl 5 as well.

#!/usr/bin/env perl
use 5.18.0;
use bignum;
use strict;
my $cnt = 0;
sub rec {
my ($y, $z) = (shift, shift);
return 108 - (815-1500/$z)/$y;
}
sub default_behavior {
my $N = shift;
my @x = ( 4, 4.25 );
for my $i (2..$N+1) {
my $n = rec($x[$i-1], $x[$i-2]);
push(@x, $n);
}
return @x;
}
my @y = default_behavior(30);
for my $i (0..30) {
say $i . " | " . $y[$i];
}

This time Perl 5 is not as impressive. This code croaks around iteration 26. Remove the use bignum pragma and it croaks around 13.[1]

Still: For most us this is just theoretical stuff, so in reality you’ll make do with both Perls I guess. At least I know I will.


NOTE
[1]
Julia, that was a part of the comparison mix in my last article, was good until iteration 52 using the BigFloat type. To have a fair comparison with Perl 6 I also tried Julia with Julia’s rational type, specified to the highest precision — Rational{UInt128}. Julia gave up at around the same iteration using that as well. So Julia fared better than Perl 5 but nowhere near Perl 6. But as I noted above about the two Perls, the difference is theoretical. For all practical purposes even Julia will do.