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

Greetings Monks,

I ported the Perl/Tk example below of factorial from Tcl/Tk, not because it is particularly interesting, but because I am working on a cryptology/encryption application, and I want to understand the Tk widget basics before tackling the more ambitious project. I'll state the problem up front, as if I simply post the code without explanation, we all might get lost in the weeds of the debugging statements I inserted to validate my issue.

Stated as succinctly as possible, I enter an integer in an Entry widget and I calculate the factorial of that number and display it in a Label widget. I bind the Entry widget to <Return> and also configure a Calc button to also call the factorial function. Unfortunately, the Label display integer overflows at any number > 2**31 -1, which means Tk's default int in that widget is a 32 bit int. In "Tcl and the Tk Toolkit" the author's call out the existence of the Tk functions wide() and entier() as means to expand the size of int used internally, but I have not been able to get either to work. There are 5 different functions that exercise calculations that can display overflow; an interesting one is exponentiation(), which switches to floating point math when the result exceeds 2**31 -1. Label displays this number correctly, but as a float, not an integer. The text suggests that Tk converts back and forth between int and float math as needed under the covers, but that does not seem to work for my two factorial() functions, one of which is recursive.

To exercise the different functions you must match a bind expression with a -command expression in the Calc button. Just uncomment the two you wish to try. Debugging statements will be written to the console. Factorial() calculations overflow at 13! while intOverFlow() overflows at 2**31.

What I hope the Monks brotherhood/sisterhood can explain is how to get Tk/Tkx to use 64 bit ints, or with entier(), ints of unlimited length?

Here be the code
#!/usr/bin/env -S perl ##!/usr/bin/env -S perl -wd use warnings; use strict; use feature ":all"; use Tkx; use Tk; sub factorial { my ($n) = @_; return 1 if $n == 0; return factorial($n-1) * $n; } sub recurWrapper { my $n = factorial(@_); say "after recursion: ", $n; return $n; } sub factorial_1 { (my $val) = @_; my $result = 1; while ( $val > 0 ) { $result *= $val; $val--; } say "\$result inside factorial_1: $result"; return $result; } sub intOverflow { (my $val) = @_; return $val *= 1; } sub exponential { (my $val) = @_; my $n = 2**$val; say "\$result inside exponential $n"; return $n; } say 'factorial(17): ', factorial(17); say 'factorial_1(17): ', factorial_1(17); say ''; say 'exponential(31): ', exponential(31); say 'exponential(32): ', exponential(32); say 'exponential(33): ', exponential(33); say ''; say 'intOverflow(2147483647): ', intOverflow(2147483647); say 'intOverflow(2147483648): ', intOverflow(2147483648); say 'intOverflow(2147483649): ', intOverflow(2147483649); my $mw = Tkx::widget->new("."); $mw->g_wm_title("Factorial Calculator"); $mw->g_wm_minsize(200, 150); my $result; my $value; my $v = $mw->new_entry( -width => 6, -relief => "sunken", -textvariable => \$value, ); #$v->Tkx::bind('<Return>', sub { say $value; $result = factorial($valu +e); say $result; } ); $v->Tkx::bind('<Return>', sub { say $value; $result = recurWrapper($va +lue); say $result; } ); #$v->Tkx::bind('<Return>', sub { say $value; $result = factorial_1($va +lue); say $result; } ); #$v->Tkx::bind('<Return>', sub { say $value; $result = exponential($va +lue); say $result; } ); #$v->Tkx::bind('<Return>', sub { say $value; $result = intOverFlow($va +lue); say $result; } ); my $desc = $mw->new_label( -text => "Factorial is:", ); my $r = $mw->new_label( -textvariable => \$result, ); my $calc = $mw->new_button( -text => "Calculate", # -command => sub { say $value; $result = factorial($value); say $ +result; }, -command => sub { say $value; $result = recurWrapper($value); sa +y $result; }, # -command => sub { say $value; $result = factorial_1($value); say $ +result; }, # -command => sub { say $value; $result = exponential($value); say $ +result; }, # -command => sub { say $value; $result = intOverflow($value); say $ +result; }, ); my $q = $mw->new_button( -text => "QUIT", -command => sub { $mw->g_destroy; }, ); Tkx::grid($v, $desc, $r, -padx => 10, -pady => 10); Tkx::grid($calc, "-", "-", -padx => 10, -pady => 5); Tkx::grid($q, "-", "-", -padx => 10, -pady => 5); Tkx::MainLoop() __END__

Thanks in advance for any insights.

Replies are listed 'Best First'.
Re: Perl/Tk int overflow in Label widget
by choroba (Cardinal) on Nov 06, 2023 at 21:19 UTC
    I've removed the Tkx stuff and used plain Tk. The trick is to use Math::BigInt for the $result, but also stringify it so Tk gets a plain old scalar string.
    #!/usr/bin/perl use warnings; use strict; use Math::BigInt; use Tk; sub factorial { my ($n) = @_; return 1 if $n == 0; no warnings 'recursion'; return factorial($n-1) * $n } my $mw = MainWindow->new(-title => 'Factorial Calculator'); my $result; my $value; my $v = $mw->Entry( -width => 6, -relief => 'sunken', -textvariable => \$value, ); my $calculate = sub { # Stringify to prevent the conversion to int or float! $result = "" . factorial(Math::BigInt->new($value)); }; $v->bind('<Return>', $calculate); my $desc = $mw->Label(-text => 'Factorial is:',); my $r = $mw->Label(-textvariable => \$result,); my $calc = $mw->Button(-text => 'Calculate', -command => $calculate); my $q = $mw->Button(-text => 'QUIT', -command => sub { $mw->DESTROY + },); Tk::grid($v, $desc, $r, -padx => 10, -pady => 10); Tk::grid($calc, '-', '-', -padx => 10, -pady => 5); Tk::grid($q, '-', '-', -padx => 10, -pady => 5); Tk::MainLoop();

    It happily computes and displays 100!.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Thank you choroba, as always, your code is clean and elegant and your solution is spot on. Also, it gives me a model that I can refer to when reading the official Tk docs re other widgets. A clear example is worth its weight in ... you fill in the rest :-)

      Regards, Will

      Hello again choroba,

      I'd like to impose again on your generosity by asking a few detailed questions about your/my code. At issue for me are some public warnings in the Tk community, foremost from Active State, to wit: favor 'use Tkx' instead of 'use Tk' because the Perl module Tk has been allowed to languish even as Tcl/Tk continues to advance. The theory there, I infer, is that the Tkx wrapper makes calls directly into the Tcl/Tk library, assuming it is installed. I have Perl Tk and perl Tkx, so could use either. The Tkx to Tcl syntax is challenging but once you get the hang of using '_' and '__' it makes sense. However, do you concur that Tkx facilitates access to more recent changes in Tk than does Perl Tk?

      As an exercise I ported your Tk solution to Tkx, as below. It looks exactly like your Tk version and yields the same answer, including 100! :-). I was just about to try sprintf() when I got your answer.

      #!/usr/bin/env -S perl ##!/usr/bin/env -S perl -d # # Script to exercise Perl Tkx/Tcl bindings # use warnings; use strict; use v5.30; use feature ":all"; use Math::BigInt; use Tkx; sub factorial { my ($n) = @_; return 1 if $n == 0; no warnings 'recursion'; return factorial($n-1) * $n } my $mw = Tkx::widget->new(".",); $mw->g_wm_title("Tkx Factorial Calculator"); my $result; my $value; my $e = $mw->new_entry( -width => 6, -relief => 'sunken', -textvariable => \$value, ); my $calculate = sub { # Stringify to prevent the conversion to int or float! say $value; $result = "" . factorial(Math::BigInt->new($value)); say $result; }; my $desc = $mw->new_label(-text => 'Factorial is:',); my $r = $mw->new_label(-textvariable => \$result,); my $blk = $mw->new_label(-text => '', -width => 8,); my $calc = $mw->new_button(-text => 'Calculate', -command => $calculat +e); my $q = $mw->new_button(-text => 'QUIT', -command => sub { $mw->g_d +estroy; },); Tkx::grid($blk, $e, $desc, $r, -padx => 10, -pady => 10); Tkx::grid($calc, -column=>3, -padx => 10, -pady => 10); Tkx::grid($q, '-', '-', -padx => 10, -pady => 15); $e->g_bind('<Return>', $calculate); $e->g_focus(); Tkx::MainLoop; __END__
      Some comments/questions about this code:

      Please notice my grid expressions. I wanted the Entry widget, $e, to appear in col2 but when I tried:

        Tkx::grid("-", $e, $desc, $r, -padx => 10, -pady => 10);
      

      the program failed to compile. I had to add that kludge $blk for 'blank' in order to push the 1st line of widgets to the right one position. I tried myriad -column, -row and -columnspan settings but could not duplicate what I was able to do with those 3 simple grid expressions. On macOS the Entry appears over the Quit, while the Calculate button is centered directly under the Result regardless how long the result string. Also, I omitted any geographic attachment/sticky settings and the default has the entire set of widgets locked in place when the window is resized; again, exactly what I was looking for and did not have to code.

      So, I now, thanks to you have a model I can build on going forward, but I am troubled by this bias in the Perl/Tk community against Perl Tk and for Tkx. What really are the issues there? Also, can you point me at some additional documentation and examples of using grid? What I have found so far seems overly complicated compared to what you and then I did with this example.

      Thanks again, Will

        Sorry, I have no experience with Tkx, I've only used Tk. What I know is that the original creator and maintainer of Tk has passed away, and that Tk is stuck with an old version of tcl/tk, missing some new widgets etc. My assumption was the problem would be the same, and I was correct.

        Regarding grid, I usually use the pack geometry manager, so I can't help you much, either. Sorry to disappoint you; hopefully another monk will be more knowledgeable.

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Thank you choroba. Spot on and exactly what I was looking for.

      Regards, Will