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

Happy new year everyone! I wrote this sub yesterday. The goal here is to perform a division with a big positive integer dividend that can be any length. Currently, this sub cannot divide two big integers. It can only divide a big integer by one digit or multiples of 10 or if the dividend and divisor are less than 16 digits. Can anyone show me how you would write BIGDIV sub that takes two big positive integers of any size? That's next on my todo list :-D

# # This function performs simple divisions on big positive integers # in base 10 if ONE of the following three conditions is true: # # * Divisor is 1 digit long (Dividend can be any length) # * Divisor is a multiple of 10 (Dividend can be any length) # * Dividend and divisor are both 15 digits or less # # The 1st argument should be the dividend, the 2nd the divisor. # The 1st return value is the quotient followed by the remainder. # # Usage: LIST = BIGDIVN(BIGINT, DIGIT) # sub BIGDIVN { defined $_[0] && defined $_[1] or return (0, 0); my $A = $_[0]; $A =~ tr|0-9||cd; # Remove illegal chars my $B = $_[1]; $B =~ tr|0-9||cd; # Remove illegal chars $A =~ s/^0+(.+)/$1/; # Remove preceding zeros $B =~ s/^0+(.+)/$1/; # Remove preceding zeros length($A) && $B or return (0, 0); # Divide by zero? $B eq '1' and return ($A, 0); # Divide by one? if ($B =~ m/^1(0+)$/) # Divide by 1, 10, 100, 1000...? { my $Z = length($1); length($A) <= $Z and return (0, $A); my $Q = substr($A, 0, length($A) - $Z); my $R = substr($A, -$Z); $Q =~ s/^0+(.+)/$1/; # Remove preceding zeros from quotient $R =~ s/^0+(.+)/$1/; # Remove preceding zeros from remainder return ($Q, $R); } elsif (length($A) < 16 && length($B) < 16) # Divide small numbers { return (int($A / $B), $A % $B); } elsif (length($B) != 1) { return (0, 0); } # We only accept simple divisons # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # Okay, here we're going to divide a big integer by a single digit. # First, we create lookup tables for processing big integers. # These lookup tables are necessary, so we can completely # avoid doing divisions or multiplications. # The first lookup table ($LUT) is for finding the quotient. # When dividing any 1-digit or 2-digit number by the divisor, # the result is going to be found in $LUT. # The second lookup table ($MUL) is a multiplication table # in which each byte is a product of the divisor going # from 0 to 9. So, if we're dividing by 6, then $MUL is going to # hold the following: "\0\6\x0B\x12\x1B\x1E\x24\x2A\x30\x36" my $S = 0; # Holds the previous multiplication's result my $i = -1; # Counter goes from 0 to 9 my $LUT = ''; # Lookup table #1 my $MUL = ''; # Lookup table #2 while (++$i < 10) { $MUL .= chr($S); # Build lookup tables... $LUT .= chr($i) x $B; $S += $B; } $A =~ tr|0-9|\x00-\x09|; # Subtract 48 from dividend's digits my $OUTPUT = ''; # This will hold the quotient my $REM = 0; # This will hold the remainder my $Q; # Current digit of the quotient $i = -1; # Counter used to read digits from dividend while (++$i < length($A)) { $S = $REM . vec($A, $i, 8); # Merge remainder with current digit $Q = vec($LUT, $S, 8); # Calculate next digit for quotient $REM = $S - vec($MUL, $Q, 8); # Calculate remainder $OUTPUT .= $Q; } $OUTPUT =~ s/^0+(.+)/$1/; # Remove preceding zeros from quotient return ($OUTPUT, $REM); } sub __Test_BIGDIVN { return RunTest( 'BIGDIVN(0, 0)', '0 0', 'BIGDIVN(0, 10000)', '0 0', 'BIGDIVN(2179014949932, 3)', '726338316644 0', 'BIGDIVN(2179014949933, 3)', '726338316644 1', 'BIGDIVN(2179014949934, 3)', '726338316644 2', 'BIGDIVN(2179014949935, 3)', '726338316645 0', 'BIGDIVN(2179014949936, 3)', '726338316645 1', 'BIGDIVN(2179014949930, 5)', '435802989986 0', 'BIGDIVN(2179014949931, 5)', '435802989986 1', 'BIGDIVN(2179014949932, 5)', '435802989986 2', 'BIGDIVN(2179014949933, 5)', '435802989986 3', 'BIGDIVN(2179014949934, 5)', '435802989986 4', 'BIGDIVN(2179014949935, 5)', '435802989987 0', 'BIGDIVN(2179014949936, 5)', '435802989987 1', 'BIGDIVN("591092491992919449491", "0")', '0 0', 'BIGDIVN("18474690051828881094465", 3)', '6158230017276293698155 0' +, 'BIGDIVN("18474690051828881094466", 3)', '6158230017276293698155 1' +, 'BIGDIVN("18474690051828881094467", 3)', '6158230017276293698155 2' +, 'BIGDIVN("18474690051828881094468", 3)', '6158230017276293698156 0' +, 'BIGDIVN("18474690051828881094469", 3)', '6158230017276293698156 1' +, 'BIGDIVN("18474690051828881094470", 3)', '6158230017276293698156 2' +, 'BIGDIVN("18474690051828881094465", 5)', '3694938010365776218893 0' +, 'BIGDIVN("18474690051828881094466", 5)', '3694938010365776218893 1' +, 'BIGDIVN("18474690051828881094467", 5)', '3694938010365776218893 2' +, 'BIGDIVN("18474690051828881094468", 5)', '3694938010365776218893 3' +, 'BIGDIVN("59109249199291944949881", 8)', '7388656149911493118735 1' +, 'BIGDIVN("59109249199291944949881", "1")', + '59109249199291944949881 0', 'BIGDIVN("59109249199291944949881", "100000000000000000")', + '591092 49199291944949881', 'BIGDIVN("59109249199291944949881", "10000000000000000000000000")', + '0 59109249199291944949881', 'BIGDIVN("50000000000000000000000", "1000000000000000")', + '50000000 0', ); } sub RunTest { @_ or return 1; my $TESTS = @_ >> 1; my $ERRORS = 0; my $PTR = 0; my $SUBNAME = ($_[0] =~ m/([a-zA-Z0-9]+)\(/) ? $1 : ''; for (my $i = 1; $i <= $TESTS; $i++) { my $CODE = $_[$PTR++]; my $CORRECT = $_[$PTR++]; my @RETURN = eval($CODE); my $RETCOUNT = @RETURN; foreach (@RETURN) { defined $_ or $_ = '<undef>'; } my $OUTPUT = join(' ', @RETURN); if ($OUTPUT ne $CORRECT) { $ERRORS++; print "\n", '-' x 78, "\nERROR #$ERRORS IN TEST $i: $CODE => |$OUTPUT| RetCount: +$RETCOUNT", "\n\n Correct: |$CORRECT|\n"; } } print( ($ERRORS) ? "\nFAIL - $SUBNAME() with $ERRORS errors." : "\nO +K - $SUBNAME()"); return ($ERRORS) ? 0 : 1; } __Test_BIGDIVN();

Replies are listed 'Best First'.
Re: Division of big integers
by LanX (Saint) on Dec 31, 2024 at 16:31 UTC
      Wait, let me save a little time for everyone:

      Re^2: Division of big integers by harangzsolt33
      I'm aware, but bigint doesn't work in TinyPerl 5.8 (justification) (justification)
      Re^3: Division of big integers by LanX
      Are you seriously going to waste the time to (rant) (rant)
      Re^4: Division of big integers by harangzsolt33
      It's not my fault that you will never realize the brilliance of TinyPerl (etc) (etc) Windows XP (etc)

      Re-implementing big-int math from scratch in decimal ascii is a small price to pay.
      Re^5: Division of big integers by LanX
      I'm done with this. Why do you keep posting about (rant) (rant)

      Re^5: Division of big integers by NERDVANA
      So... on your page about how to install Windows XP I see that in addition to TinyPerl you also install Python 3.4

      One easy fix could then be
      sub bigint_divide { my ($numerator, $divisor)= @_; chomp(my $ret= `python3.exe -c "print(int('$numerator')/int('$diviso +r'))"`); $ret; }

      Now everyone can get back to their holiday partying!

      Happy New Year!

        NERDVANA, you are the wind beneath my wings.
        I think, the conversation would have unfolded differently. But it's New Year's Eve, so let's party. Btw I haven't started learning Python yet. First I want to become a lot more familiar with Perl. I might learn Python one day, but I keep putting it off. Honestly, I hate its syntax.
      Yes, I am aware. My goal is to write this in pure perl. The goal isn't necessarily to produce the smallest perl script possible but one that is easy to understand and does perform well considering it's written in pure perl. So, I am looking for such a solution. Remember, I am just a hobby programmer, which means I am not getting paid for the lines of code I write. And I am not getting paid by the hour or by the product I produce. It's entirely for entertainment and fun. So, with that in mind, I am trying to understand how to divide a big integer by another big integer, and this is quite tricky, because a big integer means it can be hundreds of digits long, so it's not going to fit into a regular perl number. Normally, perl numbers have 15 significant digits, and anything beyond that is lost because it has to fit into 64 bits. So, therefore, using regular numbers is out of the question, I guess.
        These modules are pure Perl, that's why their performance is poor Perl. (SCNR)

        You might learn a bit by looking into them. :)

        Good luck!

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery

        PS: I seem to remember they had XS shadows, but my brain feels 2025 years old...

        Update

        Yep, multiple XS modules available, like Math::BigInt::GMP and Math::BigInt::FastCalc

        syphilis might know better about the pro and cons

Re: Division of big integers
by tybalt89 (Monsignor) on Dec 31, 2024 at 20:44 UTC

    Holiday fun !!

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11163460 use warnings; use List::AllUtils qw( zip_by before ); $SIG{__WARN__} = sub { die @_ }; sub bigdivide { my ($t, $d) = map s/^0+(?=.)//r, @_; my $q = ''; my @pad = before { length $t < length $d . 0 x $_ || length $t == $_ + length $d && $t lt $d . 0 x $_ } 0 .. length $t; for( reverse @pad ) { my $digit = 0; while( length $t > $_ + length $d || length $t == $_ + length $d && $t ge $d . 0 x $_ ) { $digit++; $t = bigsubtract( $t, $d . 0 x $_ ); } $q .= $digit; } return $q || 0; } my $xx = '123888888888888888888888888888888888888888888888455555555588 +8888888888888888888888888555555555555'; my $yy = '555333577778888888888888888888888888887555555'; my $quo = bigdivide($xx, $yy); print "bigdivide $quo\n"; { use bigint; my $tmp = ($xx+0) / ($yy+0); print " bigint $tmp\n"; } sub bigsubtract { my ($top, $bottom) = map s/^0+(?=.)//r, @_; if( length $top > length $bottom || length $top == length $bottom && $top ge $bottom ) { my @top = reverse split //, $top; my @bottom = reverse split //, $bottom; my $borrow = 0; my $answer = reverse join '', zip_by { my $digit = $_[0] - ($_[1] // 0) - $borrow; $borrow = $digit >= 0 ? 0 : 1; $digit >= 0 ? $digit : $digit % 10; } \@top, \@bottom; return $answer =~ s/^0+(?=.)//r; } print "failed\n"; use Data::Dump 'dd'; dd undef; return undef; }

    Outputs:

    bigdivide 2230891374953314563617132232227474545270207356483795 bigint 2230891374953314563617132232227474545270207356483795

    Partially tested :)

      And here's a shorter 'bigdivide'.

      sub bigdivide { my ($t, $d) = map s/^0+(?=.)//r, @_; my $q = 0; $d .= 0 x (my $zeros = length($t) - length $d); for ( 0 .. $zeros ) { $q .= 0; while( defined( my $try = bigsubtract( $t, $d ) ) ) { $t = $try; $q++; } chop $d; } return ($q || 0) =~ s/^0+(?=.)//r; }
      UPDATE: Should be 'defined' in the while :(
Re: Division of big integers
by Danny (Chaplain) on Jan 07, 2025 at 00:23 UTC