perl

 view release on metacpan or  search on metacpan

cpan/Math-BigInt/lib/Math/BigInt/Calc.pm  view on Meta::CPAN

        $step = [$step];
        while ($c->_acmp($step, $n) <= 0) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            $c->_mul($cx, $step);
            $c->_inc($step);
        }
    } else {
        # Yes, so we can speed it up slightly

        #    print "# left over steps $n\n";

        my $base_4 = int(sqrt(sqrt($BASE))) - 2;
        #print STDERR "base_4: $base_4\n";
        my $n4 = $n - 4;
        while ($step < $n4 && $step < $base_4) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            my $b = $step * ($step + 1);
            $step += 2;
            $b *= $step * ($step + 1);
            $step += 2;
            $c->_mul($cx, [$b]);
        }
        my $base_2 = int(sqrt($BASE)) - 1;
        my $n2 = $n - 2;
        #print STDERR "base_2: $base_2\n";
        while ($step < $n2 && $step < $base_2) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            my $b = $step * ($step + 1);
            $step += 2;
            $c->_mul($cx, [$b]);
        }
        # do what's left over
        while ($step <= $n) {
            $c->_mul($cx, [$step]);
            $step++;
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
        }
    }
    # multiply in the zeros again
    unshift @$cx, (0) x $zero_elements;
    $cx;                        # return result
}

sub _log_int {
    # calculate integer log of $x to base $base
    # ref to array, ref to array - return ref to array
    my ($c, $x, $base) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # BASE 0 or 1 => NaN
    return if @$base == 1 && $base->[0] < 2;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return $x, 1;
    }

    my $cmp = $c->_acmp($x, $base);

    # X == BASE => 1 (is exact)
    if ($cmp == 0) {
        @$x = 1;
        return $x, 1;
    }

    # 1 < X < BASE => 0 (is truncated)
    if ($cmp < 0) {
        @$x = 0;
        return $x, 0;
    }

    my $x_org = $c->_copy($x);  # preserve x

    # Compute a guess for the result based on:
    # $guess = int ( length_in_base_10(X) / ( log(base) / log(10) ) )
    my $len = $c->_len($x_org);
    my $log = log($base->[-1]) / log(10);

    # for each additional element in $base, we add $BASE_LEN to the result,
    # based on the observation that log($BASE, 10) is BASE_LEN and
    # log(x*y) == log(x) + log(y):
    $log += (@$base - 1) * $BASE_LEN;

    # calculate now a guess based on the values obtained above:
    my $res = $c->_new(int($len / $log));

    @$x = @$res;
    my $trial = $c->_pow($c->_copy($base), $x);
    my $acmp = $c->_acmp($trial, $x_org);

    # Did we get the exact result?

    return $x, 1 if $acmp == 0;

    # Too small?

    while ($acmp < 0) {
        $c->_mul($trial, $base);
        $c->_inc($x);
        $acmp = $c->_acmp($trial, $x_org);
    }

    # Too big?

    while ($acmp > 0) {
        $c->_div($trial, $base);
        $c->_dec($x);
        $acmp = $c->_acmp($trial, $x_org);
    }

    return $x, 1 if $acmp == 0;         # result is exact
    return $x, 0;                       # result is too small
}

sub _ilog2 {
    # calculate int(log2($x))

    # There is virtually nothing to gain from computing this any differently
    # than _log_int(), but it is important that we don't use the method
    # inherited from the parent, because that method is very slow for backend
    # libraries whose internal representation uses base 10.

    my ($c, $x) = @_;
    ($x, my $is_exact) = $c -> _log_int($x, $c -> _two());
    return wantarray ? ($x, $is_exact) : $x;
}

sub _ilog10 {
    # calculate int(log10($x))

    my ($c, $x) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    my $x_orig = $c -> _copy($x);
    my $nm1 = $c -> _len($x) - 1;

    my $xtmp = $c -> _new($nm1);
    @$x = @$xtmp;

    return $x unless wantarray;

    # See if the original $x is an exact power of 10, in which case all but the
    # most significan chunks are 0, and the most significant chunk is a power
    # of 10.

    my $is_pow10 = 1;
    for my $i (0 .. $#$x_orig - 1) {
        last unless $is_pow10 = $x_orig->[$i] == 0;
    }
    $is_pow10 &&= $x_orig->[-1] == 10**int(0.5 + log($x_orig->[-1]) / log(10));

    return wantarray ? ($x, 1) : $x if $is_pow10;
    return wantarray ? ($x, 0) : $x;
}

sub _clog2 {
    # calculate ceil(log2($x))

    my ($c, $x) = @_;

    # X == 0 => NaN

    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)

    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    my $base = $c -> _two();
    my $acmp = $c -> _acmp($x, $base);

    # X == BASE => 1 (is exact)

    if ($acmp == 0) {
        @$x = 1;
        return wantarray ? ($x, 1) : $x;
    }

    # 1 < X < BASE => 0 (is truncated)

    if ($acmp < 0) {
        @$x = 0;
        return wantarray ? ($x, 0) : $x;
    }

    # Compute a guess for the result based on:
    # $guess = int( length_in_base_10(X) / (log(base) / log(10)) )

    my $len    = $c -> _len($x);
    my $log    = log(2) / log(10);
    my $guess  = $c -> _new(int($len / $log));
    my $x_orig = $c -> _copy($x);
    @$x = @$guess;

    my $trial = $c -> _pow($c -> _copy($base), $x);
    $acmp = $c -> _acmp($trial, $x_orig);

    # Too big?

    while ($acmp > 0) {
        $c -> _div($trial, $base);
        $c -> _dec($x);
        $acmp = $c -> _acmp($trial, $x_orig);
    }

    # Too small?

    while ($acmp < 0) {
        $c -> _mul($trial, $base);
        $c -> _inc($x);
        $acmp = $c -> _acmp($trial, $x_orig);
    }

    return wantarray ? ($x, 1) : $x if $acmp == 0;      # result is exact
    return wantarray ? ($x, 0) : $x;                    # result is too small
}

sub _clog10 {
    # calculate ceil(log2($x))
    my ($c, $x) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    # Get the number of base 10 digits. $n is the desired output, except when
    # $x is an exact power of 10, in which case $n is 1 too big.

    my $n = $c -> _len($x);

    # See if $x is an exact power of 10, in which case all but the most
    # significan chunks are 0, and the most significant chunk is a power of 10.

    my $is_pow10 = 1;
    for my $i (0 .. $#$x - 1) {
        last unless $is_pow10 = $x->[$i] == 0;
    }
    $is_pow10 &&= $x->[-1] == 10**int(0.5 + log($x->[-1]) / log(10));

    $n-- if $is_pow10;

    my $xtmp = $c ->_new($n);
    @$x = @$xtmp;

    return wantarray ? ($x, 1) : $x if $is_pow10;       # result is exact
    return wantarray ? ($x, 0) : $x;                    # result is too small
}

# for debugging:
use constant DEBUG => 0;
my $steps = 0;
sub steps { $steps };

sub _sqrt {
    # square-root of $x in-place

    my ($c, $x) = @_;

    if (@$x == 1) {
        # fits into one Perl scalar, so result can be computed directly
        $x->[0] = int(sqrt($x->[0]));
        return $x;
    }

    # Create an initial guess for the square root.

    my $s;
    if (@$x % 2) {
        $s = [ (0) x ((@$x - 1) / 2), int(sqrt($x->[-1])) ];
    } else {
        $s = [ (0) x ((@$x - 2) / 2), int(sqrt($x->[-2] + $x->[-1] * $BASE)) ];
    }

    # Newton's method for the square root of y:
    #
    #                      x(n) * x(n) - y
    #     x(n+1) = x(n) - -----------------

cpan/Math-BigInt/lib/Math/BigInt/Calc.pm  view on Meta::CPAN

    }
    $x;
}

sub _from_bin {
    # convert a hex number to decimal (string, return ref to array)
    my ($c, $bs) = @_;

    # instead of converting X (8) bit at a time, it is faster to "convert" the
    # number to hex, and then call _from_hex.

    my $hs = $bs;
    $hs =~ s/^[+-]?0b//;                                # remove sign and 0b
    my $l = length($hs);                                # bits
    $hs = '0' x (8 - ($l % 8)) . $hs if ($l % 8) != 0;  # padd left side w/ 0
    my $h = '0x' . unpack('H*', pack ('B*', $hs));      # repack as hex

    $c->_from_hex($h);
}

##############################################################################
# special modulus functions

sub _modinv {

    # modular multiplicative inverse
    my ($c, $x, $y) = @_;

    # modulo zero
    if ($c->_is_zero($y)) {
        return;
    }

    # modulo one
    if ($c->_is_one($y)) {
        return $c->_zero(), '+';
    }

    my $u = $c->_zero();
    my $v = $c->_one();
    my $a = $c->_copy($y);
    my $b = $c->_copy($x);

    # Euclid's Algorithm for bgcd(), only that we calc bgcd() ($a) and the result
    # ($u) at the same time. See comments in BigInt for why this works.
    my $q;
    my $sign = 1;
    {
        ($a, $q, $b) = ($b, $c->_div($a, $b));          # step 1
        last if $c->_is_zero($b);

        my $t = $c->_add(                               # step 2:
                         $c->_mul($c->_copy($v), $q),   #  t =   v * q
                         $u);                           #      + u
        $u = $v;                                        #  u = v
        $v = $t;                                        #  v = t
        $sign = -$sign;
        redo;
    }

    # if the gcd is not 1, then return NaN
    return unless $c->_is_one($a);

    ($v, $sign == 1 ? '+' : '-');
}

sub _modpow {
    # modulus of power ($x ** $y) % $z
    my ($c, $num, $exp, $mod) = @_;

    # a^b (mod 1) = 0 for all a and b
    if ($c->_is_one($mod)) {
        @$num = 0;
        return $num;
    }

    # 0^a (mod m) = 0 if m != 0, a != 0
    # 0^0 (mod m) = 1 if m != 0
    if ($c->_is_zero($num)) {
        if ($c->_is_zero($exp)) {
            @$num = 1;
        } else {
            @$num = 0;
        }
        return $num;
    }

    # We could do the following, but it doesn't actually save any time. The
    # _copy() is needed in case $num and $mod are the same object.
    #$num = $c->_mod($c->_copy($num), $mod);

    my $acc = $c->_copy($num);
    my $t = $c->_one();

    my $expbin = $c->_to_bin($exp);
    my $len = length($expbin);
    while ($len--) {
        if (substr($expbin, $len, 1) eq '1') {  # if odd
            $t = $c->_mul($t, $acc);
            $t = $c->_mod($t, $mod);
        }
        $acc = $c->_mul($acc, $acc);
        $acc = $c->_mod($acc, $mod);
    }
    @$num = @$t;
    $num;
}

sub _gcd {
    # Greatest common divisor.

    my ($c, $x, $y) = @_;

    # gcd(0, 0) = 0
    # gcd(0, a) = a, if a != 0

    if (@$x == 1 && $x->[0] == 0) {
        if (@$y == 1 && $y->[0] == 0) {
            @$x = 0;
        } else {
            @$x = @$y;



( run in 1.470 second using v1.01-cache-2.11-cpan-5a3173703d6 )