Perlpull prose, (required reading)

Authors

Publication

Pub Details

Date

Pages

See all articles from QL Hacker's Journal 32

“a perl of great precise(sic)” , but limited accuracy.

“Optimized for text” they say of perl, that wonderful Swiss-army chainsaw programming language. But I’ve had great fun with numbers, discovering the mathematical abilities of perl for the QL, version 4.036 as ported over by Jonathan Hudson. We get answers to math problems in double precision, carried out to 14 or 15 signifigant figures, similar to ABACUS. When I tried Tim’s dice.pl (QHJ#30), and put in 2 die with 3 sides, or 3 die with 2 sides (like flipping coins), I got very strange answers. Things ($totper) didn’t add up to 100%, but would come out under or over. I discussed this with Bill Cable while at the East Coast QL show. He got good and reasonable answers on his PC laptop using Perl 5, but with QL perl 4 under QLAY he was able to get the same funny answers as I had found.

Turns out to be in the exponentiation function. This creates a floating point number, and these can cause trouble when used for counting or comparisons. Exponentiation uses natural logarithms and rounding errors in the 15th decimal place cause the bogus answers. I wrote a perl subroutine using an algorithm similar to the one in the power function from “The C Programming Language” K&R 2nd ed., section 1.8, page 27 which uses integer values and so far has given the “right” answers. Perl does not use typecasting, so we can’t declare (int)power. Nor can I try “use integer;” to see how that works.

In the dice.pl program I changed from the exponentiation operation to a call to the power subroutine and then the answers came out as expected.

But you might ask; how can we make a call to a function or procedure in perl? The answer is the use of the perl keyword “sub” before the name of the function block which is appended to the listing, and the use of the ampersand “&” before the name of the function to call it. Another choice is to write the subroutine as a file which you add to your library and can then pull it in when wanted by simply asking your main program to “require” it.

Perlpull prose – my adventures with perl pulls prose out of me.

Purple prose – expletives replete when recursing a perl problem.

Here’s the block to append to dice.pl:

sub power  {
    local($base,$exponent) = @_;
    $power = 1 - ($base == 0);
    if (($base == 0) && ($exponent == 0)) {
        $power = "NaN";
    }
    else {
         while( $exponent > 0) {
             $power *= $base;
             $exponent--;
         }
    }
    return $power;
}

To put it into the library, think of a filename for it; (I used lib_power.pl) and add:

1;

as a final last line.

In QLHJ#30 the three instances of the expression:

($sides**$num_die)

are replaced with the expression:

(&power($sides,$num_die))

Now you can either add the power subroutine (without the final 1;) to the dice.pl listing, or you can pull in the library version (with the final 1;) by having a line added at the beginning of dice.pl that reads:

require "power.pl";

which will pull it in from your lib_ subdirectory.

To see how the values compare, try this compower.pl program:

#!/usr/bin/perl
# compower.pl  for QL perl 4
# H L Schaaf August 21, 1999
# to compare the results of exponentiation in perl with# a method that multiplies an integral number of times.
$around = 1;
$log_limit = log(2**1023);
while($around){
    print "\fThis is round  ",$around;
    print "\n\n\t please ENTER a number for the base ";
    $base = &inkey.<STDIN>;
    chop $base;
    if($base) { $safe_size =
        &abs(int($log_limit/(log(&abs($base)))));
    }
    else {
        $safe_size = 2**1023;
    }
    print "\n\t(exponents larger than ",$safe_size," are
       probably too large)";
    print "\n\n\t please ENTER a positive integral number
       for the exponent ";
    $exponent = &inkey.<STDIN>;
    chop $exponent;
    $power  = &power($base,$exponent);
    print "\n\n\t       integral power  ",$power;
    $float_power = $base**$exponent;
    print "\n\n\t floating point power  ",$float_power;
    print "\n\t  -------------------------";
   print "\n\n\t        difference is  ",
$power-$float_power;
    print "\n\n\t ENTER for another, ESC (at any time) to
quit";
    &inkey;
}
continue {
    $around ++ ;
}

sub power {
    local($base,$exponent) = @_;
    $power = 1 - ($base == 0);
    if (($base == 0) && ($exponent == 0)) {
        $power = "NaN";
    }
    else {
        while( $exponent > 0) {
            $power *= $base;
            $exponent--;
        }
    }
    return $power;
}

sub inkey {
    sysread(STDIN,$inkey,1);
    if (ord($inkey) == 27) {
        print "\b \n\n\n\t\t";
        exit;
    }
    return $inkey;
}

sub sgn {
    local($n) = @_;
    return ($n <=> 0);
}

sub abs {
    local($n) = @_;
    return ($n * &sgn($n));
}

Other folks have written all sorts of things for perl that can be “required” and used. Two numerically interesting examples, bigint.pl and bigfloat.pl (which itself pulls in bigint.pl), are in the library provided by Jonathan Hudson. I found them fun to noodle with, so why not give them a try if you’re into math and want to see results carried out with great precision. You can set the number of significant digits to be “arbitrarily (?)” large.

Here is the result of my noodling around:

#!/usr/bin/perl
# bigflopintdemo.pl bigfloat.pl and bigint.pl in QL perl 4
# H L Schaaf August 21, 1999
print " a small demo of big floating point and big integer
operations in perl";
print "\n please wait for required module(s) to be pulled in
from the library";
require "bigfloat.pl";
$around = 1;
while($around){
    print "\f this is round ",$around;
    print "\n please ENTER the first number  ";
    $n1 = &inkey.<STDIN>;
    chop $n1;
    $valid_answer = 0;
    print "\n choose an operation by touching the
appropriate key\n";
    print "\n  [P]lus, [M]inus, [T]imes, [D]ivided by ";
    print "\n  [R]aise to an integral power, [S]quare root
(these take time)";
    print "\n\t\t   [G]reatest common denominator\t";
    while( !$valid_answer){
        $op = &inkey;
        if ($op =~ /[pPmMtTdDrRsSgG]/) {$valid_answer = 1;}
        print "\b \b";
    }
    print "\n\n";
    if($op =~ /[sSdD]/) {
        print "\n  How many signifigant digits wanted ?  ";
        $sig_digits = &inkey.<STDIN>;
        chop $sig_digits;
    }
    else {
        $sig_digits = 1;
    }
    if ($op =~ /[sS]/) {
        print "\n\t\t please wait \n";
        $started = time;
        $f = &fsqrt($n1,$sig_digits);
    }
    else {
        print "\n please ENTER the second number  ";
        $n2 = &inkey.<STDIN>;
        chop $n2;
     if ($op =~ /[rR]/) {print "\n\t\t   please wait \n";  }
     if ($op =~ /[pP]/) {$f = &fadd($n1,$n2,$sig_digits);  }
     if ($op =~ /[mM]/) {$f = &fsub($n1,$n2,$sig_digits);  }
     if ($op =~ /[tT]/) {$f = &fmul($n1,$n2,$sig_digits);  }
     if ($op =~ /[dD]/) {$f = &fdiv($n1,$n2,$sig_digits);  }
        if ($op =~ /[gG]/) {$f = &bgcd($n1,$n2)    ;  }
        if ($op =~ /[rR]/) {$started = time; $f =
&bpow($n1,$n2); }
    }
    if ($op =~ /[rRsS]/) {
        $elapsed_time = time - $started;
        print "\n\t  that took about ",$elapsed_time,"
second", (($elapsed_time == 1) ? " " : "s"),"\n";

    }
    print "\n",$f,"\n";
    print "\n ",&withdecimal($f);
    print "\n touch ENTER for another demo  or ESC (at any
time) to exit ";
    &inkey;
    print "\f";
}
continue {
    $around ++ ;
}

sub inkey {
    sysread(STDIN,$inkey,1);
    if (ord($inkey) == 27){
        print "\b \n\n\n\t\t";
        exit;
    }
    return $inkey;
}

sub withdecimal {
    local($bigfloat) = @_ ;
    local($number,$exponent) = split('E',$bigfloat);
    $decimal_place = (length($number)) + $exponent;
    if ($exponent > 0) {
        $number = $number.("0" x ($exponent));
    }
    if ($decimal_place>1){
        $bigfloat_with_decimal = 
            substr($number,0,$decimal_place)
            .".".substr($number,$decimal_place);
    }
    else {
        $number_lead =substr($number,0,1)."0.";
        $zeros = "0" x (1 - $decimal_place);
        $bigfloat_with_decimal = 
          $number_lead.$zeros.substr($number,1);
    }
    return $bigfloat_with_decimal;
}

sub bpow {
    local($bbase,$bexponent) = @_;
    $bpower = 1 - ($bbase == 0);
    if(($bexponent == 0) && ($bbase == 0)) {
        $bpower = "NaN";
    }
    else {
        while ($bexponent) {
            $bpower = &fmul($bbase,$bpower);
            $bexponent--;
        }
    }
    return &fnorm($bpower);
}

Imagine how we could embellish this by adding trig and other math functions. We could even create a general purpose (“general perlplus”?) scientific calculator with store, recall, memory registers and such; maybe it has been done already and is on CPAN. Of course half the fun is in writing a program yourself, and the other half is debugging and getting it to work.

I tend to think in in BASIC, my first programming language, so have tried to find ways to get results in perl that are comparable to some of the S*BASIC commands. Here are some equivalents that seem to work:

INKEY$(-1)

I like to have an interactive menu sometimes, and just want the same action as we enjoy with INKEY$(-1) in S*BASIC. I finally found a way to do it on the QL in perl. This gets a key from the user without them having to touch ENTER. The perl keyword is sysread.

inkey.pl is an example that will detect the ESC key from the user

# sysread as a way to read inkey without use of ENTER
# inkey.pl

while(1){
    sysread(STDIN,$raw,1);
    if ( ord($raw) eq 27 ) {
        print "\b  \t";
    exit;
    }
    else {
        $ans = $raw.<STDIN>;
    }
    chop($ans);
    print "\n";
    print length($ans);
    print "\n",$ans,"\n";
    if (length($ans)==1){
        if ($ans =~ /[yY]/) {
            print "\n That's a Yes \n";
        }
        if ($ans =~ /[nN]/) {
            print "\n That's a No \n";
        }
    }
}

I can’t edit the first digit of numbers when I use this inkey.pl, maybe there is a work-around like the getch and ungetch in C ? When using QTPI as a link to UNIX at the University of Delaware, this inkey subroutine behaves differently; it still works, but seems to be anticipated or read-ahead in the script. There’s probably a better way; how would you do it?

REPeat  END REPeat$around = 1;
while ($around) { }

In my explorations of perl programs I often want to keep trying different inputs to see how things go, without having perl exit after my first exploration. By putting everything inside in braces after a while(1) I’m able to get the effect that REPeat , END REPeat has.

exit;

works anytime to break out of the while($around), so we can test for some condition (like the ESC key being touched) to end a session.

I use the continue block to keep count of the trips around the while loop.

continue {
    $around ++;
}

I believe that nearly the same effect (except for the continue) could be accomplished very simply with the -n or -p switch on the command line. And of course we could use for loops too. TMTOWTDI or tim-toady as they say in perl; “There’s More Than One Way To Do It.”

CLS
          print "\f";

or formfeed, does the trick when we want a “clean slate”.

DATE

example:        $now = time;

the keyword in perl is time.

perl’s calendar starts in 1970 instead of the QL’s 1961;

I used the time function in bigflopintdemo.pl to see how long it took to extract square roots or raise to integral powers with the bigfloat.pl and bigint.pl libraries. It took 220 seconds to raise 2 to the 1024th power, and 11 seconds to get the square root of 2 to 100 digits, with the Super Gold Card. With a Gold Card it took 681 seconds for the power and 28 seconds for the root. It took 2 seconds for the power and 0 for the root with Perl 5.005_02 running under sun 4 solaris using my University of Delaware UNIX account via QTPI. It was nice to see the same program work on both QL’s and under the UNIX setup.

When running compower.pl the UNIX results were equal with no difference between the exponentiation operator and the power subroutine. So perhaps Perl 5 does something with integer exponentiation that Perl 4 does not.

PAUSE(power_cycles)

         sleep(seconds);

perl’s sleep is measured in seconds; the QL PAUSE counts the power line cycles. If no parameter is given both will wait forever. perl has alarm(seconds); but I haven’t sussed it out yet. How do you regain control in perl if you’ve done sleep(); ?

CODE("character in quotes") in the QL

is equivalent to         ord($chr)  in perl

this returns the ASCII code for a character.

chr($ascii) in perl is equivalent to CHR$(ascii) in the QL and returns the character for the ASCII code.

We could start a module of these equivalents and conversions between QL S*Basics and perl and put them into our library as well. We might also try to build an associative array %QLBASIC_perl_hash following the example of Bill Cable’s “English-Spanish convertor” and see how they work and learn how to add more terms to the list.

perl has other ways to pull in snippets, scripts, etc. and I wonder how the keywords ‘do’, ‘eval’, and ‘use’ work. Anybody want to give us some examples? Oh, there’s a LABEL: concept in perl that uses ‘next’, ‘last’, and ‘redo’. Anybody want to show us how those work? How about the termcap.pl items, can we control the cursor, do ASCII graphics ? What about bigrat.pl ?

What perl features have you found fun or useful ?

Products

 

Downloadable Media

 

Image Gallery

Scroll to Top