The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use strict;
use warnings;

use Test::More tests => 356;;

use Math::BigInt::GMP;

# testing of Math::BigInt::GMP

my $LIB = 'Math::BigInt::GMP';            # pass classname to sub's

# _new and _str
my $x = $LIB->_new("123");
my $y = $LIB->_new("321");
is(ref($x), 'Math::BigInt::GMP');
is($LIB->_str($x), 123);
is($LIB->_str($y), 321);

###############################################################################

note "_set()";

my $b = $LIB->_new("123");
$LIB->_set($b, 12);
is($LIB->_str($b), 12);

###############################################################################

note "_add(), _sub(), _mul(), _div()";

is($LIB->_str($LIB->_add($x, $y)), 444);
is($LIB->_str($LIB->_sub($x, $y)), 123);
is($LIB->_str($x), 123);
is($LIB->_str($y), 321);
is($LIB->_str($LIB->_mul($x, $y)), 39483);
is($LIB->_str(scalar $LIB->_div($x, $y)), 123);

# check that mul/div doesn't change $y
# and returns the same reference, not something new

is($LIB->_str($LIB->_mul($x, $y)), 39483);
is($LIB->_str($x), 39483);
is($LIB->_str($y), 321);

is($LIB->_str(scalar $LIB->_div($x, $y)), 123);
is($LIB->_str($x), 123);
is($LIB->_str($y), 321);

$x = $LIB->_new("39483");
my ($x1, $r1) = $LIB->_div($x, $y);
is("$x1", "$x");
$LIB->_inc($x1);
is("$x1", "$x");
is($LIB->_str($r1), '0');

# check that sub modifies the right argument:

$x = $LIB->_new("221");
$y = $LIB->_new("444");

$x = $LIB->_sub($y, $x, 1);                       # 444 - 221 => 223

is($LIB->_str($x), 223);
is($LIB->_str($y), 444);

$x = $LIB->_new("444");
$y = $LIB->_new("221");

is($LIB->_str($LIB->_sub($x, $y)), 223);    # 444 - 221 => 223

is($LIB->_str($x), 223);
is($LIB->_str($y), 221);

###############################################################################

$x = $LIB->_new("39483"); # reset
$y = $LIB->_new("321");   # reset

my $z = $LIB->_new("2");
is($LIB->_str($LIB->_add($x, $z)), 39485);
my ($re, $rr) = $LIB->_div($x, $y);

is($LIB->_str($re), 123);
is($LIB->_str($rr), 2);

##############################################################################

note "is_zero()";

is($LIB->_is_zero($x) || 0, 0);

note "_is_one()";

is($LIB->_is_one($x)  || 0, 0);

note "_one()";

is($LIB->_str($LIB->_zero()), "0");

note "_zero()";

is($LIB->_str($LIB->_one()),  "1");

##############################################################################

note "_two()";

is($LIB->_str($LIB->_two()), "2");
is($LIB->_is_ten($LIB->_two()), 0);
is($LIB->_is_two($LIB->_two()), 1);

note "_ten()";

is($LIB->_str($LIB->_ten()), "10");
is($LIB->_is_ten($LIB->_ten()), 1);
is($LIB->_is_two($LIB->_ten()), 0);

is($LIB->_is_one($LIB->_one()), 1);
is($LIB->_is_one($LIB->_two()), 0);
is($LIB->_is_one($LIB->_ten()), 0);

is($LIB->_is_one($LIB->_zero()) || 0, 0);

is($LIB->_is_zero($LIB->_zero()), 1);

is($LIB->_is_zero($LIB->_one()) || 0, 0);

###############################################################################

note "is_odd()";

is($LIB->_is_odd($LIB->_one()), 1);
is($LIB->_is_odd($LIB->_zero()) || 0, 0);

note "is_even()";

is($LIB->_is_even($LIB->_one()) || 0, 0);
is($LIB->_is_even($LIB->_zero()), 1);

###############################################################################

note "_len() and _alen()";

sub _check_len {
    my ($str, $method) = @_;

    my $n  = length($str);
    my $x = $LIB->_new($str);

    # _len() is exact
    is($LIB->_len($x), $n);

    # _alen() is equal or at most one bigger
    my $alen = $LIB->_alen($x);
    ok($n -1 <= $alen && $alen <= $n + 1,
       qq|\$x = $LIB->_new("$str"); $LIB->_alen(\$x)|)
      or diag sprintf <<"EOF", $alen, $n - 1, $n, $n + 1;
         got: '%d'
    expected: '%d', '%d' or '%d'
EOF
}

_check_len("1");
_check_len("12");
_check_len("123");
_check_len("1234");
_check_len("12345");
_check_len("123456");
_check_len("1234567");
_check_len("12345678");
_check_len("123456789");
_check_len("1234567890");
_check_len("7");
_check_len("8");
_check_len("9");
_check_len("10");
_check_len("11");
_check_len("21");
_check_len("321");
_check_len("320");
_check_len("4321");
_check_len("54321");
_check_len("654321");
_check_len("7654321");
_check_len("7654321");
_check_len("87654321");
_check_len("987654321");
_check_len("9876543219876543210");
_check_len("1234567890" x 10);
_check_len("1234567890" x 100);

for (my $i = 1; $i < 9; $i++) {
    my $a = "$i" . '0' x ($i-1);
    _check_len($a);
}

###############################################################################

note "_digit()";

$x = $LIB->_new("123456789");
is($LIB->_digit($x, 0), 9);
is($LIB->_digit($x, 1), 8);
is($LIB->_digit($x, 2), 7);
is($LIB->_digit($x, -1), 1);
is($LIB->_digit($x, -2), 2);
is($LIB->_digit($x, -3), 3);

###############################################################################

note "_copy()";

foreach (qw/ 1 12 123 1234 12345 123456 1234567 12345678 123456789/) {
    $x = $LIB->_new("$_");
    is($LIB->_str($LIB->_copy($x)), "$_");
    is($LIB->_str($x), "$_");     # did _copy destroy original x?
}

###############################################################################

note "_zeros()";

$x = $LIB->_new("1256000000");
is($LIB->_zeros($x), 6);

$x = $LIB->_new("152");
is($LIB->_zeros($x), 0);

$x = $LIB->_new("123000");
is($LIB->_zeros($x), 3);

$x = $LIB->_new("123001");
is($LIB->_zeros($x), 0);

$x = $LIB->_new("1");
is($LIB->_zeros($x), 0);

$x = $LIB->_new("8");
is($LIB->_zeros($x), 0);

$x = $LIB->_new("10");
is($LIB->_zeros($x), 1);

$x = $LIB->_new("11");
is($LIB->_zeros($x), 0);

$x = $LIB->_new("0");
is($LIB->_zeros($x), 0);

###############################################################################

note "_lsft()";

$x = $LIB->_new("10");
$y = $LIB->_new("3");
is($LIB->_str($LIB->_lsft($x, $y, 10)), 10000);

$x = $LIB->_new("20");
$y = $LIB->_new("3");
is($LIB->_str($LIB->_lsft($x, $y, 10)), 20000);

$x = $LIB->_new("128");
$y = $LIB->_new("4");
is($LIB->_str($LIB->_lsft($x, $y, 2)), 128 << 4);

note "_rsft()";

$x = $LIB->_new("1000");
$y = $LIB->_new("3");
is($LIB->_str($LIB->_rsft($x, $y, 10)), 1);

$x = $LIB->_new("20000");
$y = $LIB->_new("3");
is($LIB->_str($LIB->_rsft($x, $y, 10)), 20);

$x = $LIB->_new("256");
$y = $LIB->_new("4");
is($LIB->_str($LIB->_rsft($x, $y, 2)), 256 >> 4);

$x = $LIB->_new("6411906467305339182857313397200584952398");
$y = $LIB->_new("45");
is($LIB->_str($LIB->_rsft($x, $y, 10)), 0);

###############################################################################

note "_acmp()";

$x = $LIB->_new("123456789");
$y = $LIB->_new("987654321");
is($LIB->_acmp($x, $y), -1);
is($LIB->_acmp($y, $x), 1);
is($LIB->_acmp($x, $x), 0);
is($LIB->_acmp($y, $y), 0);
$x = $LIB->_new("12");
$y = $LIB->_new("12");
is($LIB->_acmp($x, $y), 0);
$x = $LIB->_new("21");
is($LIB->_acmp($x, $y), 1);
is($LIB->_acmp($y, $x), -1);
$x = $LIB->_new("123456789");
$y = $LIB->_new("1987654321");
is($LIB->_acmp($x, $y), -1);
is($LIB->_acmp($y, $x), +1);

$x = $LIB->_new("1234567890123456789");
$y = $LIB->_new("987654321012345678");
is($LIB->_acmp($x, $y), 1);
is($LIB->_acmp($y, $x), -1);
is($LIB->_acmp($x, $x), 0);
is($LIB->_acmp($y, $y), 0);

$x = $LIB->_new("1234");
$y = $LIB->_new("987654321012345678");
is($LIB->_acmp($x, $y), -1);
is($LIB->_acmp($y, $x), 1);
is($LIB->_acmp($x, $x), 0);
is($LIB->_acmp($y, $y), 0);

###############################################################################

note "_modinv()";

$x = $LIB->_new("8");
$y = $LIB->_new("5033");
my ($xmod, $sign) = $LIB->_modinv($x, $y);
is($LIB->_str($xmod), '4404');
           # (4404 * 8) % 5033 = 1
is($sign, '+');

###############################################################################

note "_div()";

$x = $LIB->_new("3333");
$y = $LIB->_new("1111");
is($LIB->_str(scalar $LIB->_div($x, $y)), 3);
$x = $LIB->_new("33333");
$y = $LIB->_new("1111");
($x, $y) = $LIB->_div($x, $y);
is($LIB->_str($x), 30);
is($LIB->_str($y), 3);
$x = $LIB->_new("123");
$y = $LIB->_new("1111");
($x, $y) = $LIB->_div($x, $y);
is($LIB->_str($x), 0);
is($LIB->_str($y), 123);

###############################################################################

note "_num()";

foreach (qw/1 12 123 1234 12345 1234567 12345678 123456789 1234567890/) {
    $x = $LIB->_new("$_");
    is(ref($x) || '', 'Math::BigInt::GMP');
    is($LIB->_str($x), "$_");
    $x = $LIB->_num($x);
    is(ref($x) || '', '');
    is($x, $_);
}

###############################################################################

note "_sqrt()";

$x = $LIB->_new("144");
is($LIB->_str($LIB->_sqrt($x)), '12');
$x = $LIB->_new("144000000000000");
is($LIB->_str($LIB->_sqrt($x)), '12000000');

###############################################################################

note "_root()";

$x = $LIB->_new("81");
my $n = $LIB->_new("3");
is($LIB->_str($LIB->_root($x, $n)), '4');   # 4*4*4 = 64, 5*5*5 = 125

$x = $LIB->_new("81");
$n = $LIB->_new("4");
is($LIB->_str($LIB->_root($x, $n)), '3');   # 3*3*3*3 == 81

###############################################################################

note "_pow() (and _root())";

$x = $LIB->_new("0");
$n = $LIB->_new("3");
is($LIB->_str($LIB->_pow($x, $n)), 0);      # 0 ** y => 0

$x = $LIB->_new("3");
$n = $LIB->_new("0");
is($LIB->_str($LIB->_pow($x, $n)), 1);      # x ** 0 => 1

$x = $LIB->_new("1");
$n = $LIB->_new("3");
is($LIB->_str($LIB->_pow($x, $n)), 1);      # 1 ** y => 1

$x = $LIB->_new("5");
$n = $LIB->_new("1");
is($LIB->_str($LIB->_pow($x, $n)), 5);      # x ** 1 => x

$x = $LIB->_new("81");
$n = $LIB->_new("3");

is($LIB->_str($LIB->_pow($x, $n)), 81 ** 3);    # 81 ** 3 == 531441

is($LIB->_str($LIB->_root($x, $n)), 81);

$x = $LIB->_new("81");
is($LIB->_str($LIB->_pow($x, $n)), 81 ** 3);
is($LIB->_str($LIB->_pow($x, $n)), '150094635296999121');   # 531441 ** 3 ==

is($LIB->_str($LIB->_root($x, $n)), '531441');
is($LIB->_str($LIB->_root($x, $n)), '81');

$x = $LIB->_new("81");
$n = $LIB->_new("14");
is($LIB->_str($LIB->_pow($x, $n)), '523347633027360537213511521');
is($LIB->_str($LIB->_root($x, $n)), '81');

$x = $LIB->_new("523347633027360537213511520");
is($LIB->_str($LIB->_root($x, $n)), '80');

$x = $LIB->_new("523347633027360537213511522");
is($LIB->_str($LIB->_root($x, $n)), '81');

my $res = [ qw/ 9 31 99 316 999 3162 9999/ ];

# 99 ** 2 = 9801, 999 ** 2 = 998001 etc
for my $i (2 .. 9) {
    $x = '9' x $i;
    $x = $LIB->_new($x);
    $n = $LIB->_new("2");
    my $rc = '9' x ($i-1). '8' . '0' x ($i-1) . '1';
    is($LIB->_str($LIB->_pow($x, $n)), $rc,
       "_pow(" . ('9' x $i) . ", 2)");

    if ($i <= 7) {
        $x = '9' x $i;
        $x = $LIB->_new($x);
        $n = '9' x $i;
        $n = $LIB->_new($n);
        is($LIB->_str($LIB->_root($x, $n)), '1',
           "_root(" . ('9' x $i) . ", " . (9 x $i) . ")");


        $x = '9' x $i;
        $x = $LIB->_new($x);
        $n = $LIB->_new("2");
        is($LIB->_str($LIB->_root($x, $n)), $res->[$i-2],
           "_root(" . ('9' x $i) . ", " . (9 x $i) . ")");
    }
}

##############################################################################
# _fac

$x = $LIB->_new("0");
is($LIB->_str($LIB->_fac($x)), '1');

$x = $LIB->_new("1");
is($LIB->_str($LIB->_fac($x)), '1');

$x = $LIB->_new("2");
is($LIB->_str($LIB->_fac($x)), '2');

$x = $LIB->_new("3");
is($LIB->_str($LIB->_fac($x)), '6');

$x = $LIB->_new("4");
is($LIB->_str($LIB->_fac($x)), '24');

$x = $LIB->_new("5");
is($LIB->_str($LIB->_fac($x)), '120');

$x = $LIB->_new("10");
is($LIB->_str($LIB->_fac($x)), '3628800');

$x = $LIB->_new("11");
is($LIB->_str($LIB->_fac($x)), '39916800');

$x = $LIB->_new("12");
is($LIB->_str($LIB->_fac($x)), '479001600');

$x = $LIB->_new("13");
is($LIB->_str($LIB->_fac($x)), '6227020800');

# test that _fac modifes $x in place for small arguments

$x = $LIB->_new("3");  $LIB->_fac($x); is($LIB->_str($x), '6');
$x = $LIB->_new("13"); $LIB->_fac($x); is($LIB->_str($x), '6227020800');

##############################################################################

note "_inc() and _dec()";

foreach (qw/1 11 121 1231 12341 1234561 12345671 123456781 1234567891/) {
    $x = $LIB->_new("$_");
    $LIB->_inc($x);
    print "# \$x = ", $LIB->_str($x), "\n"
      unless is($LIB->_str($x), substr($_, 0, length($_)-1) . '2');
    $LIB->_dec($x);
    is($LIB->_str($x), $_);
}

foreach (qw/19 119 1219 12319 1234519 12345619 123456719 1234567819/) {
    $x = $LIB->_new("$_");
    $LIB->_inc($x);
    print "# \$x = ", $LIB->_str($x), "\n"
      unless is($LIB->_str($x), substr($_, 0, length($_)-2) . '20');
    $LIB->_dec($x);
    is($LIB->_str($x), $_);
}

foreach (qw/999 9999 99999 9999999 99999999 999999999 9999999999 99999999999/) {
    $x = $LIB->_new("$_");
    $LIB->_inc($x);
    print "# \$x = ", $LIB->_str($x), "\n"
      unless is($LIB->_str($x), '1' . '0' x (length($_)));
    $LIB->_dec($x);
    is($LIB->_str($x), $_);
}

$x = $LIB->_new("1000");
$LIB->_inc($x);
is($LIB->_str($x), '1001');
$LIB->_dec($x);
is($LIB->_str($x), '1000');

###############################################################################

note "_log_int()";

# test handling of plain scalar as base, bug up to v1.17)

$x = $LIB->_new("81");

my ($r, $exact) = $LIB->_log_int($x, $LIB->_new("3"));
is($LIB->_str($r), '4');
ok($LIB->_str($x) eq '81' || $LIB->_str($x) eq '4');
is($exact, 1);

$x = $LIB->_new("81");

($r, $exact) = $LIB->_log_int($x, 3);
is($LIB->_str($r), '4');
ok($LIB->_str($x) eq '81' || $LIB->_str($x) eq '4');
is($exact, 1);

###############################################################################

note "_mod()";

$x = $LIB->_new("1000");
$y = $LIB->_new("3");
is($LIB->_str(scalar $LIB->_mod($x, $y)), 1);
$x = $LIB->_new("1000");
$y = $LIB->_new("2");
is($LIB->_str(scalar $LIB->_mod($x, $y)), 0);

###############################################################################

note "_and(), _or(), _xor()";

$x = $LIB->_new("5");
$y = $LIB->_new("2");
is($LIB->_str(scalar $LIB->_xor($x, $y)), 7);
$x = $LIB->_new("5");
$y = $LIB->_new("2");
is($LIB->_str(scalar $LIB->_or($x, $y)), 7);
$x = $LIB->_new("5");
$y = $LIB->_new("3");
is($LIB->_str(scalar $LIB->_and($x, $y)), 1);

###############################################################################

note "_from_hex() and _from_bin()";

is($LIB->_str($LIB->_from_hex("0xFf")), 255);
is($LIB->_str($LIB->_from_bin("0b10101011")), 160+11);

###############################################################################

note "_as_hex() and _as_bin()";

is($LIB->_str($LIB->_from_hex($LIB->_as_hex($LIB->_new("128")))), 128);
is($LIB->_str($LIB->_from_bin($LIB->_as_bin($LIB->_new("128")))), 128);
is($LIB->_str($LIB->_from_hex($LIB->_as_hex($LIB->_new("0")))), 0);
is($LIB->_str($LIB->_from_bin($LIB->_as_bin($LIB->_new("0")))), 0);
is($LIB->_as_hex($LIB->_new("0")), '0x0');
is($LIB->_as_bin($LIB->_new("0")), '0b0');
is($LIB->_as_hex($LIB->_new("12")), '0xc');
is($LIB->_as_bin($LIB->_new("12")), '0b1100');

###############################################################################

note "_from_oct()";

$x = $LIB->_from_oct("001");      is($LIB->_str($x), '1');
$x = $LIB->_from_oct("07");       is($LIB->_str($x), '7');
$x = $LIB->_from_oct("077");      is($LIB->_str($x), '63');
$x = $LIB->_from_oct("07654321"); is($LIB->_str($x), '2054353');

###############################################################################

note "_as_oct()";

$x = $LIB->_new("2054353"); is($LIB->_as_oct($x), '07654321');
$x = $LIB->_new("63");      is($LIB->_as_oct($x), '077');
$x = $LIB->_new("0");       is($LIB->_as_oct($x), '00');

###############################################################################

note "_1ex()";

is($LIB->_str($LIB->_1ex(0)), "1");
is($LIB->_str($LIB->_1ex(1)), "10");
is($LIB->_str($LIB->_1ex(2)), "100");
is($LIB->_str($LIB->_1ex(12)), "1000000000000");
is($LIB->_str($LIB->_1ex(16)), "10000000000000000");

###############################################################################

note "_check()";

$x = $LIB->_new("123456789");
is($LIB->_check($x), 0);
is($LIB->_check(123), '123 is not a reference to Math::BigInt::GMP');

# done

1;