package Math::BigInt::GMPz;
use 5.006002;
use strict;
use warnings;
use Math::BigInt::Lib 1.999801;
our @ISA = qw< Math::BigInt::Lib >;
our $VERSION = '0.0018';
use Math::GMPz 0.36 qw< :mpz >;
BEGIN {
# We need GMP 5.1.0 or newer for Rmpz_2fac_ui(). If Rmpz_2fac_ui() is not
# implemented, Math::GMPz dies with the message:
# "Rmpz_2fac_ui not implemented - gmp-5.1.0 (or later) is needed"
my $gmp_v = Math::GMPz::gmp_v();
my @gmp_v = split /\./, $gmp_v;
my $gmp_v_int = 1e6 * $gmp_v[0] + 1e3 * $gmp_v[1] + $gmp_v[0];
die "GMP library is not recent enough;",
" we have $gmp_v, but we need 5.1.0 or later"
unless $gmp_v_int >= 5_100_000;
}
###############################################################################
sub import { }
my $zero = Rmpz_init(); # for _is_zero
my $one = Rmpz_init_set_str("1", 10); # for _is_one, _inc, and _dec
my $two = Rmpz_init_set_str("2", 10); # for _is_two
my $ten = Rmpz_init_set_str("10", 10); # for _is_ten, _digit
sub api_version { 2; }
sub _new {
Rmpz_init_set_str($_[1], 10);
}
sub _zero {
Rmpz_init();
}
sub _one {
Rmpz_init_set_str("1", 10);
}
sub _two {
Rmpz_init_set_str("2", 10);
}
sub _ten {
Rmpz_init_set_str("10", 10);
}
sub _from_bin {
my $str = $_[1];
$str =~ s/^0[Bb]//; # remove leading '0b'
Rmpz_init_set_str($str, 2);
}
sub _from_oct {
Rmpz_init_set_str($_[1], 8);
}
#sub _from_dec {
# Rmpz_init_set_str($_[1], 10);
#}
sub _from_hex {
my $str = $_[1];
$str =~ s/^0[Xx]//; # remove leading '0x'
Rmpz_init_set_str($str, 16);
}
sub _from_bytes {
my $rop = Rmpz_init();
my $bstr = $_[1];
my $len = length $bstr;
my ($order, $size, $endian, $nails) = (1, 1, 0, 0);
Rmpz_import($rop, $len, $order, $size, $endian, $nails, $bstr);
return $rop;
}
sub _from_base {
my $class = shift;
# If a collation sequence is given, pass everything to parent.
return $class -> SUPER::_from_base(@_) if @_ == 3;
# If base > 36, pass everything to parent.
my $str = $_[0];
my $base = $_[1];
$base = $class -> _new($base) unless ref($base);
if ($class -> _acmp($base, $class -> _new("36")) > 0) {
return $class -> SUPER::_from_base($str, $base);
} else {
return Rmpz_init_set_str($str, $base);
}
}
sub _1ex {
Rmpz_init_set_str("1" . ("0" x $_[1]));
}
sub _add {
Rmpz_add($_[1], $_[1], $_[2]);
return $_[1];
}
sub _mul {
Rmpz_mul($_[1], $_[1], $_[2]);
return $_[1];
}
sub _div {
if (wantarray) {
my $r = Rmpz_init();
Rmpz_fdiv_qr($_[1], $r, $_[1], $_[2]);
return ($_[1], $r);
}
Rmpz_fdiv_q($_[1], $_[1], $_[2]);
return $_[1];
}
sub _sub {
if ($_[3]) {
$_[2] = $_[1] - $_[2];
return $_[2];
}
Rmpz_sub($_[1], $_[1], $_[2]);
return $_[1];
}
sub _dec {
Rmpz_sub_ui($_[1], $_[1], 1);
return $_[1];
}
sub _inc {
Rmpz_add_ui($_[1], $_[1], 1);
return $_[1];
}
sub _mod {
Rmpz_fdiv_r($_[1], $_[1], $_[2]);
return $_[1];
};
sub _sqrt {
Rmpz_sqrt($_[1], $_[1]);
return $_[1];
}
sub _root {
Rmpz_root($_[1], $_[1], $_[2]);
return $_[1];
}
sub _fac {
Rmpz_fac_ui($_[1], $_[1]);
return $_[1];
}
sub _dfac {
Rmpz_2fac_ui($_[1], $_[1]);
return $_[1];
}
sub _pow {
Rmpz_pow_ui($_[1], $_[1], $_[2]);
return $_[1];
}
sub _modinv {
my $bool = Rmpz_invert($_[1], $_[1], $_[2]);
return $_[1], '+' if $bool;
return;
}
sub _modpow {
Rmpz_powm($_[1], $_[1], $_[2], $_[3]);
return $_[1];
}
sub _rsft {
# (X, N, B) = @_; means X >> N in base B (= X / B^N)
# N must be an unsigned integer.
my $n = ref($_[2]) ? Rmpz_get_ui($_[2]) : $_[2];
if ($_[3] == 2) {
Rmpz_div_2exp($_[1], $_[1], $n);
} else {
# B must be a Math::GMPz object.
my $b = ref($_[3]) ? $_[3] : Rmpz_init_set_ui($_[3]);
my $p = Rmpz_init();
Rmpz_pow_ui($p, $b, $n); # $p = $b**$n
Rmpz_div($_[1], $_[1], $p);
}
return $_[1];
}
sub _lsft {
# (X, N, B) = @_; means X << N in base B (= X * B^N)
# N must be an unsigned integer.
my $n = ref($_[2]) ? Rmpz_get_ui($_[2]) : $_[2];
if ($_[3] == 2) {
Rmpz_mul_2exp($_[1], $_[1], $n);
} else {
# B must be a Math::GMPz object.
my $b = ref($_[3]) ? $_[3] : Rmpz_init_set_ui($_[3]);
my $p = Rmpz_init();
Rmpz_pow_ui($p, $b, $n); # $p = $b**$n
Rmpz_mul($_[1], $_[1], $p);
}
return $_[1];
}
sub _log_int {
my ($class, $x, $b) = @_;
# Return undef for the logarithm of zero.
return if $class -> _is_zero($x);
# Rmpz_sizeinbase() requires that the base is <= 62.
my $bscl; # the base as a Perl scalar
if (ref $b) {
$bscl = $class -> _num($b);
} else {
($bscl, $b) = ($b, $class -> _new($b));
}
# A base < 2 is invalid.
return if $bscl < 2;
# A base > 62 can't be handled by Math::GMPz.
return $class -> SUPER::_log_int($x, $b) if $bscl > 62;
# Rmpz_sizeinbase() returns a Perl scalar that is either 1 or 2 too big
# compared to the output we want, i.e., int(log(x) / log(b)).
my $y = Rmpz_sizeinbase($x, $bscl);
$y = $class -> _new($y - 1);
# To determine whether we need to subtract one more, we need to go
# backwards and compute $b ** $y, unfortunately.
my $trial = $class -> _pow($class -> _copy($b), $y);
my $acmp = $class -> _acmp($trial, $x);
# Did we get the exact result?
return wantarray ? ($y, 1) : $y if $acmp == 0;
# Decrement $y once more, if the output was too large.
$y = $class -> _dec($y) if $acmp > 0;
return $y unless wantarray;
# If the user wants to know whether the output is exact or not, we need to
# update $acmp after the decrement above.
$trial = $class -> _div($trial, $b);
$acmp = $class -> _acmp($trial, $x);
return wantarray ? ($y, 1) : $y if $acmp == 0; # result is exact
return wantarray ? ($y, 0) : $y; # result is too small
}
sub _ilog2 {
my ($class, $x) = @_;
# Return undef for the logarithm of zero.
return if $class -> _is_zero($x);
# Rmpz_sizeinbase() returns a Perl scalar that is either 1 or 2 too big
# compared to the output we want, i.e., int(log(x) / log(2)).
my $y = Rmpz_sizeinbase($x, 2) - 1;
# To determine whether we need to subtract one more, we need to go
# backwards and compute 2 ** $y, unfortunately.
my $trial = Rmpz_init_set_str("1", 10); # $trial = 1
Rmpz_mul_2exp($trial, $trial, $y); # $trial = 2**$y
# Make $y an object.
$y = Rmpz_init_set_ui($y);
# Did we get the exact result?
my $acmp = Rmpz_cmp($trial, $x);
return wantarray ? ($y, 1) : $y if $acmp == 0;
# Decrement $y once more, if the output was too large.
Rmpz_sub_ui($y, $y, 1) if $acmp > 0; # $y -= 1
return $y unless wantarray;
# If the user wants to know whether the output is exact or not, we need to
# update $acmp after the decrement above.
Rmpz_div_2exp($trial, $trial, 1);
$acmp = Rmpz_cmp($trial, $x);
return wantarray ? ($y, 1) : $y if $acmp == 0; # result is exact
return wantarray ? ($y, 0) : $y; # result is too small
}
sub _clog2 {
my ($class, $x) = @_;
# Return undef for the logarithm of zero.
return if $class -> _is_zero($x);
# Rmpz_sizeinbase() returns a Perl scalar that is either correct or 1 too
# big compared to the output we want, i.e., ceil(log(x) / log(2)).
my $y = Rmpz_sizeinbase($x, 2) - 1; # assume 1 too big
# Go backwards and compute 2 ** $y.
my $trial = Rmpz_init_set_str("1", 10); # $trial = 1
Rmpz_mul_2exp($trial, $trial, $y); # $trial = 2**$y
$y = Rmpz_init_set_ui($y); # make $y an object
# Did we get the exact result?
my $acmp = Rmpz_cmp($trial, $x);
return wantarray ? ($y, 1) : $y if $acmp == 0;
# Increment $y by one, if the output was too small.
Rmpz_add_ui($y, $y, 1) if $acmp < 0; # $y += 1
return $y unless wantarray;
# If the user wants to know whether the output is exact or not, we need to
# update $acmp after the increment above.
Rmpz_mul_2exp($trial, $trial, 1);
$acmp = Rmpz_cmp($trial, $x);
return wantarray ? ($y, 1) : $y if $acmp == 0; # result is exact
return wantarray ? ($y, 0) : $y; # result is too small
}
sub _gcd {
Rmpz_gcd($_[1], $_[1], $_[2]);
return $_[1];
}
sub _lcm {
Rmpz_lcm($_[1], $_[1], $_[2]);
return $_[1];
}
sub _and {
Rmpz_and($_[1], $_[1], $_[2]);
return $_[1];
}
sub _or {
Rmpz_ior($_[1], $_[1], $_[2]);
return $_[1];
}
sub _xor {
Rmpz_xor($_[1], $_[1], $_[2]);
return $_[1];
}
sub _is_zero {
!Rmpz_cmp($_[1], $zero);
}
sub _is_one {
!Rmpz_cmp($_[1], $one);
}
sub _is_two {
!Rmpz_cmp($_[1], $two);
}
sub _is_ten {
!Rmpz_cmp($_[1], $ten);
}
sub _is_even {
Rmpz_even_p($_[1]);
}
sub _is_odd {
Rmpz_odd_p($_[1]);
}
sub _acmp {
Rmpz_cmp($_[1], $_[2]);
}
sub _str {
Rmpz_get_str($_[1], 10);
}
sub _as_bin {
'0b' . Rmpz_get_str($_[1], 2);
}
sub _as_oct {
'0' . Rmpz_get_str($_[1], 8);
}
sub _as_hex {
'0x' . Rmpz_get_str($_[1], 16);
}
sub _to_bin {
Rmpz_get_str($_[1], 2);
}
sub _to_oct {
Rmpz_get_str($_[1], 8);
}
#sub _to_dec {
# Rmpz_get_str($_[1], 10);
#}
sub _to_hex {
Rmpz_get_str($_[1], 16);
}
sub _to_bytes {
my ($class, $x) = @_;
return "\x00" if $class -> _is_zero($x);
my ($order, $size, $endian, $nails) = (1, 1, 0, 0);
Rmpz_export($order, $size, $endian, $nails, $x);
}
*_as_bytes = \&_to_bytes;
sub _to_base {
my $class = shift;
# If a collation sequence is given, pass everything to parent.
return $class -> SUPER::_to_base(@_) if @_ == 3;
# If base > 36, pass everything to parent.
my $str = $_[0];
my $base = $_[1];
$base = $class -> _new($base) unless ref($base);
if ($class -> _acmp($base, $class -> _new("36")) > 0) {
return $class -> SUPER::_to_base($str, $base);
} else {
return uc Rmpz_get_str($str, $base);
}
}
sub _num {
0 + Rmpz_get_str($_[1], 10);
}
sub _copy { Rmpz_init_set($_[1]); }
sub _len {
length Rmpz_get_str($_[1], 10);
}
sub _zeros {
return 0 unless Rmpz_cmp($_[1], $zero); # 0 has no trailing zeros
Rmpz_get_str($_[1], 10) =~ /(0*)\z/;
return length($1);
}
sub _digit {
substr(Rmpz_get_str($_[1], 10), -($_[2]+1), 1);
#if ($_[2] >= 0) {
# return( ($_[1] / (10 ** $_[2])) % 10);
#} else {
# substr(Rmpz_get_str($_[1], 10), -($_[2]+1), 1);
#}
}
sub _check {
my ($class, $x) = @_;
return "Undefined" unless defined $x;
return "$x is not a reference to Math::GMPz"
unless ref($x) eq 'Math::GMPz';
return 0;
}
sub _nok {
my ($class, $n, $k) = @_;
# If k > n/2, use the fact that binomial(n, k) = binomial(n, n-k). To avoid
# division, don't test k > n/2, but rather 2*k > n.
{
my $twok = Rmpz_init(); #
Rmpz_mul_2exp($twok, $k, 1); # $twok = 2 * $k
if (Rmpz_cmp($twok, $n) > 0) { # if 2*k > n
$k = Rmpz_init_set($k); # copy k
Rmpz_sub($k, $n, $k); # k = n - k
}
}
Rmpz_bin_ui($n, $n, $k);
return $n;
}
sub _fib {
if (wantarray) {
$_[0] -> SUPER::_fib($_[1]);
} else {
Rmpz_fib_ui($_[1], $_[1]);
return $_[1];
}
}
sub _lucas {
if (wantarray) {
$_[0] -> SUPER::_lucas($_[1]);
} else {
Rmpz_lucnum_ui($_[1], $_[1]);
return $_[1];
}
}
# XXX TODO: calc len in base 2 then appr. in base 10
sub _alen {
Rmpz_sizeinbase($_[1], 10);
}
# _set() - set an already existing object to the given scalar value
sub _set {
Rmpz_set($_[1], $_[2]);
return $_[1];
}
1;
__END__
=pod
=head1 NAME
Math::BigInt::GMPz - a math backend library based on Math::GMPz
=head1 SYNOPSIS
# to use it with Math::BigInt
use Math::BigInt lib => 'GMPz';
# to use it with Math::BigFloat
use Math::BigFloat lib => 'GMPz';
# to use it with Math::BigRat
use Math::BigRat lib => 'GMPz';
=head1 DESCRIPTION
Math::BigInt::GMPz is a backend library for Math::BigInt, Math::BigFloat,
Math::BigRat and related modules. It is not indended to be used directly.
Math::BigInt::GMPz uses Math::GMPz objects for the calculations. Math::GMPz is
an XS layer on top of the very fast gmplib library. See https://gmplib.org/
Math::BigInt::GMPz inherits from Math::BigInt::Lib.
=head1 METHODS
The following methods are implemented.
=over
=item _new()
=item _zero()
=item _one()
=item _two()
=item _ten()
=item _from_bin()
=item _from_oct()
=item _from_hex()
=item _from_bytes()
=item _from_base()
=item _1ex()
=item _add()
=item _mul()
=item _div()
=item _sub()
=item _dec()
=item _inc()
=item _mod()
=item _sqrt()
=item _root()
=item _fac()
=item _dfac()
=item _pow()
=item _modinv()
=item _modpow()
=item _rsft()
=item _lsft()
=item _log_int()
=item _ilog2()
=item _clog2()
=item _gcd()
=item _lcm()
=item _and()
=item _or()
=item _xor()
=item _is_zero()
=item _is_one()
=item _is_two()
=item _is_ten()
=item _is_even()
=item _is_odd()
=item _acmp()
=item _str()
=item _as_bin()
=item _as_oct()
=item _as_hex()
=item _to_bin()
=item _to_oct()
=item _to_hex()
=item _to_bytes()
=item _to_base()
=item _num()
=item _copy()
=item _len()
=item _zeros()
=item _digit()
=item _check()
=item _nok()
=item _fib()
=item _lucas()
=item _alen()
=item _set()
=back
=head1 BUGS
Please report any bugs or feature requests to
C<bug-math-bigint-gmpz at rt.cpan.org>, or through the web interface at
L<https://rt.cpan.org/Ticket/Create.html?Queue=Math-BigInt-GMPz>
(requires login). We will be notified, and then you'll automatically be
notified of progress on your bug as I make changes.
=head1 SUPPORT
After installing, you can find documentation for this module with the perldoc
command.
perldoc Math::BigInt::GMPz
You can also look for information at:
=over 4
=item GitHub
L<https://github.com/pjacklam/p5-Math-BigInt-GMPz>
=item RT: CPAN's request tracker
L<https://rt.cpan.org/Dist/Display.html?Name=Math-BigInt-GMPz>
=item MetaCPAN
L<https://metacpan.org/release/Math-BigInt-GMPz>
=item CPAN Testers Matrix
L<http://matrix.cpantesters.org/?dist=Math-BigInt-GMPz>
=back
=head1 LICENSE
This program is free software; you may redistribute it and/or modify it under
the same terms as Perl itself.
=head1 AUTHOR
Peter John Acklam E<lt>[email protected]<gt>
L<Math::GMPz> was written by Sisyphus Sisyphus
E<lt>sisyphus at(@) cpan dot (.) orgE<gt>
=head1 SEE ALSO
End user libraries L<Math::BigInt>, L<Math::BigFloat>, L<Math::BigRat>, as well
as L<bigint>, L<bigrat>, and L<bignum>.
Other backend libraries, e.g., L<Math::BigInt::Calc>,
L<Math::BigInt::FastCalc>, L<Math::BigInt::GMP>, and L<Math::BigInt::Pari>.
=cut