package Sidef::Types::Number::Number;

use utf8;
use 5.016;

use parent qw(
  Sidef::Object::Object
);

use Sidef::Types::Bool::Bool;

use Math::GMPz qw();
use Math::GMPq qw();
use Math::MPFR qw();
use Math::MPC  qw();

use List::Util   qw();
use Scalar::Util qw();

use Math::Prime::Util::GMP 0.53 qw();

our ($ROUND, $PREC, $USE_PRIMECOUNT, $USE_PRIMESUM, $USE_PARI_GP, $USE_YAFU, $USE_PFGW, $USE_FACTORDB, $VERBOSE, $SPECIAL_FACTORS, $USE_CONJECTURES);

BEGIN {
    $ROUND           = Math::MPFR::MPFR_RNDN();    # rounding mode for floating-point numbers
    $PREC            = 192;                        # precision in bits for floating-point numbers
    $USE_PRIMESUM    = 0;                          # true to use Kim Walisch's primesum for large n
    $USE_PRIMECOUNT  = 0;                          # true to use Kim Walisch's primecount for large n
    $USE_PFGW        = 0;                          # true to use PFGW64 as a primality pretest for large enough n
    $USE_PARI_GP     = 0;                          # true to use PARI/GP in several methods where it excels
    $USE_YAFU        = 0;                          # true to use YAFU for factoring large integers
    $USE_FACTORDB    = 0;                          # true to use factordb.com for factoring large integers
    $VERBOSE         = 0;                          # true to enable verbose/debug mode
    $USE_CONJECTURES = 0;                          # true to use conjectured methods for better performance
    $SPECIAL_FACTORS = 1;                          # true to check for special factors in `_factor(n)`
}

state $MONE = Math::GMPz::Rmpz_init_set_si(-1);
state $ZERO = Math::GMPz::Rmpz_init_set_ui( 0);
state $ONE  = Math::GMPz::Rmpz_init_set_ui( 1);
state $TWO  = Math::GMPz::Rmpz_init_set_ui( 2);
state $TEN  = Math::GMPz::Rmpz_init_set_ui(10);

state $MPC_VERSION = Math::MPC::MPC_VERSION();

state $TRUE  = Sidef::Types::Bool::Bool::TRUE;
state $FALSE = Sidef::Types::Bool::Bool::FALSE;

my %FACTOR_EXP_CACHE;
my @FACTOR_EXP_QUEUE;

my %FACTOR_CACHE;
my @FACTOR_QUEUE;

my %DIVISORS_CACHE;
my @DIVISORS_QUEUE;

my %IS_PRIME_CACHE;
my @IS_PRIME_QUEUE;

my %PRIMEPI_LOOKUP;    # lookup table for prime_count()
my @SMALL_PRIMES_20 = (3, 5, 7, 11, 13, 17, 19);

my %DISPATCH_TAG = (
                    ''           => 'Scalar',
                    'Math::GMPz' => 'Math_GMPz',
                    'Math::GMPq' => 'Math_GMPq',
                    'Math::MPFR' => 'Math_MPFR',
                    'Math::MPC'  => 'Math_MPC',
                   );

my %NUMBER_CLASSES = (
                      'Sidef::Types::Number::Mod'           => 1,
                      'Sidef::Types::Number::Gauss'         => 1,
                      'Sidef::Types::Number::Fraction'      => 1,
                      'Sidef::Types::Number::Quadratic'     => 1,
                      'Sidef::Types::Number::Quaternion'    => 1,
                      'Sidef::Types::Number::Polynomial'    => 1,
                      'Sidef::Types::Number::PolynomialMod' => 1,
                     );

use constant {

    ONE   => bless(\(my $one   = 1)),
    TWO   => bless(\(my $two   = 2)),
    THREE => bless(\(my $three = 3)),
    FOUR  => bless(\(my $four  = 4)),
    TEN   => bless(\(my $ten   = 10)),
    ZERO  => bless(\(my $zero  = 0)),
    MONE  => bless(\(my $mone  = -1)),

    ULONG_MAX => Math::GMPq::_ulong_max(),
    LONG_MIN  => Math::GMPq::_long_min(),

    # ULONG_MAX => 4294967295,
    # LONG_MIN  => -2147483647,

    FAST_MODE => 1,    # true to use fast paths for native integers

    HAS_PRIME_UTIL => eval {
        require Math::Prime::Util;
        $Math::Prime::Util::VERSION >= 0.74
          and Math::Prime::Util::prime_get_config()->{'xs'};
    } // 0,            # version >= 0.74 + XS required
};

my @SMALL_INTS;

BEGIN {
    @SMALL_INTS = (ZERO, ONE, TWO, THREE, FOUR, (map { bless \$_ } (5 .. 256)), MONE);
}

use constant {

    SMALL_INTS_SIZE => $#SMALL_INTS,

    PARI_GP_MIN            => 40,     # in decimal digits
    YAFU_MIN               => 49,     # in decimal digits
    FACTORDB_MIN           => 65,     # in decimal digits
    SPECIAL_FACTORS_MIN    => 36,     # in decimal digits (must be greater than SMALL_NUMBER_MAX_BITS)
    SMALL_NUMBER_MAX_BITS  => 110,    # in bits (numbers that can be factorized fast)
    MEDIUM_NUMBER_MAX_BITS => 150,    # in bits (numbers that can be factorized moderately fast)

    # Check if we have a recent enough version of Math::Prime::Util (>= 0.75)
    HAS_NEW_PRIME_UTIL => (HAS_PRIME_UTIL and defined(&Math::Prime::Util::sopfr)) // 0,

    # HAS_NEWER_PRIME_UTIL => (HAS_PRIME_UTIL and defined(&Math::Prime::Util::nth_powerfree))    // 0,

    # Check if we have a recent enough version of Math::Prime::Util::GMP
    # HAS_NEW_PRIME_UTIL_GMP => defined(&Math::Prime::Util::GMP::nth_powerfree) // 0,

    FACTOR_EXP_CACHE_SIZE => 1000,
    FACTOR_CACHE_SIZE     => 1000,
    DIVISORS_CACHE_SIZE   => 100,

    IS_PRIME_CACHE_SIZE   => 1000,    # how many entries to cache
    PRIMALITY_PRETEST_MIN => 500,     # in decimal digits

    INTSIZE        => List::Util::max(32, CORE::int(CORE::log(ULONG_MAX) / CORE::log(2))),    # size of ULONG_MAX in base 2
    PRIMECOUNT_MIN => List::Util::min(ULONG_MAX,       (HAS_PRIME_UTIL ? 1e10 : 1e7)),        # absolute value
    PRIMESUM_MIN   => List::Util::min(ULONG_MAX >> 14, (HAS_PRIME_UTIL ? 1e8  : 1e5)),        # absolute value

    # Tune parameters for prime_count() function
    SIEVE_INLINE_MAX => 1e7,                                                                  # use GMP::sieve_primes directly up to this diff
    SIEVE_UTIL_MAX   => 1e8,                                                                  # hand off to Math::Prime::Util up to this diff
    NAIVE_LOOP_MAX   => 1000,                                                                 # walk primes one-by-one for deltas this small
};

if (HAS_PRIME_UTIL) {
    Math::Prime::Util::prime_precalc(1e7);
    eval { Math::Prime::Util::prime_set_config(bigint => 'Math::GMPz') };
}

state $round_z = Math::MPFR::MPFR_RNDZ();

my %DIGITS_36;
@DIGITS_36{0 .. 9, 'a' .. 'z'} = (0 .. 35);

my %DIGITS_62;
@DIGITS_62{0 .. 9, 'A' .. 'Z', 'a' .. 'z'} = (0 .. 61);

my @FROM_DIGITS_36 = (0 .. 9, 'a' .. 'z');
my @FROM_DIGITS_62 = (0 .. 9, 'A' .. 'Z', 'a' .. 'z');

state $LUCAS_PQ_LIMIT = CORE::int(CORE::sqrt(ULONG_MAX >> 2));

use overload
  q{bool} => sub { (@_) = (${$_[0]}); goto &__boolify__ },
  q{0+}   => sub { (@_) = (${$_[0]}); goto &__numify__ },
  q{""}   => sub { (@_) = (${$_[0]}); goto &__stringify__ };

sub new {
    my (undef, $num, $base) = @_;

    if (ref($_[0]) eq __PACKAGE__) {
        return $_[0];
    }

    if (defined($base) and ref($base)) {
        if (ref($base) eq __PACKAGE__) {
            $base = _any2ui($$base) // 0;
        }
        else {
            $base = CORE::int($base);
        }
    }

    my $ref = ref($num);

    if ($ref eq 'Sidef::Types::String::String') {
        $num = "$$num";
        $ref = '';
    }

    # Optimization: return faster for base-10 integers
    if (!defined($base) and !$ref and $num =~ /^-?[0-9]++\z/) {

        if ($num eq '0') {
            return ZERO;
        }
        elsif ($num eq '1') {
            return ONE;
        }
        elsif ($num eq '-1') {
            return MONE;
        }
        elsif ($num eq '2') {
            return TWO;
        }

        return _set_int($num);
    }

    if (exists($NUMBER_CLASSES{$ref})) {
        $num = $num->to_n;
    }
    elsif ($ref eq 'Sidef::Types::Bool::Bool') {
        $num = $num->get_value;
    }

    # Number with base
    if (defined($base)) {

        my $int_base = CORE::int($base);

        if ($int_base < 2 or $int_base > 62) {
            die "[ERROR] Number(): base must be between 2 and 62, got $base";
        }

        $num = defined($num) ? "$num" : '0';

        # Remove the leading plus sign (if any)
        $num =~ s/^\+// if substr($num, 0, 1) eq '+';

        if (index($num, '/') != -1) {
            my $r = Math::GMPq::Rmpq_init();
            eval { Math::GMPq::Rmpq_set_str($r, $num, $int_base); 1 } // goto &nan;
            if (Math::GMPq::Rmpq_get_str($r, 10) !~ m{^\s*[-+]?[0-9]+\s*(?>/\s*[-+]?[1-9]+[0-9]*\s*)?\z}) {
                goto &nan;
            }
            Math::GMPq::Rmpq_canonicalize($r);
            return bless \$r;
        }
        elsif (substr($num, 0, 1) eq '(' and substr($num, -1) eq ')') {
            my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
            eval { Math::MPC::Rmpc_set_str($r, $num, $int_base, $ROUND); 1 } // goto &nan;
            return bless \$r;
        }
        elsif (index($num, '.') != -1) {
            my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            if (Math::MPFR::Rmpfr_set_str($r, $num, $int_base, $ROUND) < 0) {
                goto &nan;
            }
            return bless \$r;
        }
        else {
            state $t = Math::GMPz::Rmpz_init_nobless();
            eval { Math::GMPz::Rmpz_set_str($t, "$num", $int_base) };
            $@ && goto &nan;
            my $r =
                Math::GMPz::Rmpz_fits_ulong_p($t) ? Math::GMPz::Rmpz_get_ui($t)
              : Math::GMPz::Rmpz_fits_slong_p($t) ? Math::GMPz::Rmpz_get_si($t)
              :                                     Math::GMPz::Rmpz_init_set($t);
            return bless \$r;
        }
    }

    # Special string values
    if (!$ref) {
        return bless \_str2obj($num);
    }

    # Already a __PACKAGE__ object
    if ($ref eq __PACKAGE__) {
        return $num;
    }

    # GMPz
    if ($ref eq 'Math::GMPz') {
        return bless \(my $value = Math::GMPz::Rmpz_init_set($num));
    }

    # MPFR
    if ($ref eq 'Math::MPFR') {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set($r, $num, $ROUND);
        return bless \$r;
    }

    # MPC
    if ($ref eq 'Math::MPC') {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set($r, $num, $ROUND);
        return bless \$r;
    }

    # GMPq
    if ($ref eq 'Math::GMPq') {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set($r, $num);
        return bless \$r;
    }

    bless \_str2obj("$num");
}

*call = \&new;

sub with_value {
    my ($self, $value) = @_;
    ref($self)->new($value);
}

sub run { $_[1] }

sub _valid {
    (
     ref($$_) eq __PACKAGE__
       or do {
         my $sub = overload::Method($$_, '0+');

         my $tmp = (
             defined($sub) ? __PACKAGE__->new($sub->($$_)) : do {
                 my (undef, undef, undef, $caller) = caller(1);
                 if (($$_ // '') eq '') {
                     my ($func_name) = $caller =~ /::([^:]+)\z/;
                     print STDERR "[ERROR] Probably not enough arguments provided to $func_name()?\n";
                 }
                 die "[ERROR] Value <<$$_>> cannot be implicitly converted to a number, inside <<$caller>>!\n";
             }
         );

         if (ref($tmp) ne __PACKAGE__) {    # this should not happen
             my (undef, undef, undef, $caller) = caller(1);
             die "[ERROR] Cannot convert <<$$_>> to a number, inside <<$caller>>! (is method \"to_n\" well-defined?)\n";
         }

         $$_ = $tmp;
       }
    ) for @_;
}

sub _set_int {
    if (ref($_[0]) eq 'Math::GMPz') {

        if (Math::GMPz::Rmpz_fits_slong_p($_[0])) {
            return bless \(my $o = Math::GMPz::Rmpz_get_si($_[0]));
        }
        elsif (Math::GMPz::Rmpz_fits_ulong_p($_[0])) {
            return bless \(my $o = Math::GMPz::Rmpz_get_ui($_[0]));
        }

        return bless \(my $value = Math::GMPz::Rmpz_init_set($_[0]));
    }
    (!ref($_[0]) and $_[0] < ULONG_MAX and $_[0] > LONG_MIN)
      ? (($_[0] >= -1 and $_[0] < SMALL_INTS_SIZE) ? $SMALL_INTS[$_[0]] : (bless \(my $o = 0 + $_[0])))
      : (bless \(my $value = Math::GMPz::Rmpz_init_set_str($_[0], 10)));
}

sub _dump {
    my $x = ${$_[0]};

    my $ref = ref($x);

    if (!$ref) {
        ('int', $x);
    }
    elsif ($ref eq 'Math::GMPz') {
        ('int', Math::GMPz::Rmpz_get_str($x, 10));
    }
    elsif ($ref eq 'Math::GMPq') {
        ('rat', Math::GMPq::Rmpq_get_str($x, 10));
    }
    elsif ($ref eq 'Math::MPFR') {
        ('float', Math::MPFR::Rmpfr_get_str($x, 10, 0, $ROUND));
    }
    elsif ($ref eq 'Math::MPC') {
        ('complex', Math::MPC::Rmpc_get_str(10, 0, $x, $ROUND));
    }
    else {
        die "[ERROR] This shouldn't happen: <<$x>> as <<$ref>>";
    }
}

sub _set_str {
    my ($type, $str) = @_;

    if ($type eq 'int') {

        if ($str < ULONG_MAX and $str > LONG_MIN) {
            return (bless \(my $o = 0 + $str));
        }

        bless \(my $value = Math::GMPz::Rmpz_init_set_str("$str", 10));
    }
    elsif ($type eq 'rat') {
        Math::GMPq::Rmpq_set_str((my $r = Math::GMPq::Rmpq_init()), "$str", 10);
        bless \$r;
    }
    elsif ($type eq 'float') {
        Math::MPFR::Rmpfr_set_str((my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC))), "$str", 10, $ROUND);
        bless \$r;
    }
    elsif ($type eq 'complex') {
        Math::MPC::Rmpc_set_str((my $r = Math::MPC::Rmpc_init2(CORE::int($PREC))), "$str", 10, $ROUND);
        bless \$r;
    }
    else {
        die "[ERROR] Number._set_str(): invalid type <<$type>> with content <<$str>>";
    }
}

sub _str2frac {
    my $str = lc($_[0]);

    my $sign = substr($str, 0, 1);
    if ($sign eq '-') {
        substr($str, 0, 1, '');
        $sign = '-';
    }
    else {
        substr($str, 0, 1, '') if ($sign eq '+');
        $sign = '';
    }

    if ((my $i = index($str, 'e')) != -1) {

        my $exp = substr($str, $i + 1);

        my ($before, $after) = split(/\./, substr($str, 0, $i));

        if (!defined($after)) {    # return faster for numbers like "13e2"
            if ($exp >= 0) {
                return ("$sign$before" . ('0' x $exp));
            }
            else {
                $after = '';
            }
        }

        my $numerator = "$sign$before$after";

        if ($exp < 0) {
            return ("$numerator/1" . ('0' x (CORE::abs($exp) + CORE::length($after))));
        }

        my $diff = ($exp - CORE::length($after));

        if ($diff >= 0) {
            return ($numerator . ('0' x $diff));
        }

        my $s = "$before$after";
        substr($s, $exp + CORE::length($before), 0, '.');
        return __SUB__->("$sign$s");
    }

    if ((my $i = index($str, '.')) != -1) {
        my ($before, $after) = (substr($str, 0, $i), substr($str, $i + 1));

        if ($after == 0) {
            return "$sign$before";
        }

        return ("$sign$before$after/1" . ('0' x CORE::length($after)));
    }

    return "$sign$str";
}

#
## Misc internal functions
#

# Convert a given pair (real, imag) into an MPC object
sub _reals2mpc {
    my ($re, $im) = @_;

    my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));

    $re = _str2obj($re);
    $im = _str2obj($im);

    my $sig = join(' ', ref($re), ref($im));

    if ($sig eq q{Math::MPFR Math::MPFR}) {
        Math::MPC::Rmpc_set_fr_fr($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPz Math::GMPz}) {
        Math::MPC::Rmpc_set_z_z($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPz Math::MPFR}) {
        Math::MPC::Rmpc_set_z_fr($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::MPFR Math::GMPz}) {
        Math::MPC::Rmpc_set_fr_z($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPz Math::GMPq}) {
        Math::MPC::Rmpc_set_z_q($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPq Math::GMPz}) {
        Math::MPC::Rmpc_set_q_z($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPq Math::GMPq}) {
        Math::MPC::Rmpc_set_q_q($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::GMPq Math::MPFR}) {
        Math::MPC::Rmpc_set_q_fr($r, $re, $im, $ROUND);
    }
    elsif ($sig eq q{Math::MPFR Math::GMPq}) {
        Math::MPC::Rmpc_set_fr_q($r, $re, $im, $ROUND);
    }
    elsif (ref($re) eq 'Math::MPC') {
        Math::MPC::Rmpc_set($r, _any2mpc($im), $ROUND);
        Math::MPC::Rmpc_mul_i($r, $r, 1, $ROUND);
        Math::MPC::Rmpc_add($r, $r, $re, $ROUND);
    }
    elsif (ref($im) eq 'Math::MPC') {
        Math::MPC::Rmpc_set($r, $im, $ROUND);
        Math::MPC::Rmpc_mul_i($r, $r, 1, $ROUND);
        Math::MPC::Rmpc_add($r, $r, _any2mpc($re), $ROUND);
    }
    else {
        $re = _any2mpfr($re);
        $im = _any2mpfr($im);
        Math::MPC::Rmpc_set_fr_fr($r, $re, $im, $ROUND);
    }

    return $r;
}

# Converts a string into an object
sub _str2obj {
    my ($s) = @_;

    $s || return 0;

    $s = CORE::lc($s);

    # Remove underscores and whitespace
    $s =~ tr/_ \a\b\t\n\f\r\e//d;

    if ($s eq 'inf' or $s eq '+inf') {
        goto &_inf;
    }
    elsif ($s eq '-inf') {
        goto &_ninf;
    }
    elsif ($s eq 'nan') {
        goto &_nan;
    }

    # Performance improvement for Perl integers
    if (FAST_MODE and CORE::int($s) eq $s and $s > LONG_MIN and $s < ULONG_MAX) {
        return $s + 0;

#<<<
        #~ return (
                #~ $s < 0
                #~ ? Math::GMPz::Rmpz_init_set_si($s)
                #~ : Math::GMPz::Rmpz_init_set_ui($s)
               #~ );
#>>>
    }

    # Decimal expansion (parsed as a rational value)
    if ($s =~ tr/e.// and $s =~ /^([+-]?+(?=\.?[0-9])[0-9_]*+(?:\.[0-9_]++)?(?:[Ee](?:[+-]?+[0-9_]+))?)\z/) {
        my $frac = _str2frac($1);

        if (FAST_MODE and CORE::int($frac) eq $frac and $frac > LONG_MIN and $frac < ULONG_MAX) {
            return $frac + 0;
        }

        if (index($frac, '/') != -1) {
            my $q = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_set_str($q, $frac, 10);
            Math::GMPq::Rmpq_canonicalize($q);
            return $q;
        }
        else {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_set_str($z, $frac, 10);
            return $z;
        }
    }

    # Complex number of form: "(3 4)"
    if (substr($s, 0, 1) eq '(' and substr($s, -1) eq ')') {
        my ($re, $im) = split(' ', substr($s, 1, -1));

        if (defined($re) and defined($im)) {
            return _reals2mpc($re, $im);
        }
    }

    # Complex number of form: "3+4i"
    if (substr($s, -1) eq 'i') {

        if ($s eq 'i' or $s eq '+i') {
            my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
            Math::MPC::Rmpc_set_ui_ui($r, 0, 1, $ROUND);
            return $r;
        }
        elsif ($s eq '-i') {
            my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
            Math::MPC::Rmpc_set_si_si($r, 0, -1, $ROUND);
            return $r;
        }

        my ($re, $im);

        state $numeric_re  = qr/[+-]?+(?=\.?[0-9])[0-9]*+(?:\.[0-9]++)?(?:[Ee](?:[+-]?+[0-9]+))?/;
        state $unsigned_re = qr/(?=\.?[0-9])[0-9]*+(?:\.[0-9]++)?(?:[Ee](?:[+-]?+[0-9]+))?/;

        if ($s =~ /^($numeric_re)\s*([-+])\s*($unsigned_re)i\z/o) {
            ($re, $im) = ($1, $3);
            $im = "-$im" if $2 eq '-';
        }
        elsif ($s =~ /^($numeric_re)i\z/o) {
            ($re, $im) = (0, $1);
        }
        elsif ($s =~ /^($numeric_re)\s*([-+])\s*i\z/o) {
            ($re, $im) = ($1, 1);
            $im = -1 if $2 eq '-';
        }

        if (defined($re) and defined($im)) {
            return _reals2mpc($re, $im);
        }
    }

#<<<
    # Decimal expansion (parsed as a floating-point value)
    #~ if ($s =~ tr/e.//) {
        #~ my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        #~ if (Math::MPFR::Rmpfr_set_str($r, $s, 10, $ROUND)) {
            #~ Math::MPFR::Rmpfr_set_nan($r);
        #~ }
        #~ return $r;
    #~ }
#>>>

    # Remove a leading plus sign
    $s =~ s/^\+// if substr($s, 0, 1) eq '+';

    # Fractional value
    if (index($s, '/') != -1 and $s =~ m{^\s*[-+]?[0-9]+\s*/\s*[-+]?[1-9]+[0-9]*\s*\z}) {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_str($r, $s, 10);
        Math::GMPq::Rmpq_canonicalize($r);
        return $r;
    }

    eval { Math::GMPz::Rmpz_init_set_str("$s", 10) } // goto &_nan;
}

#
## MPZ
#
sub _mpz2mpq {
    my $r = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_z($r, $_[0]);
    $r;
}

sub _mpz2mpfr {
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set_z($r, $_[0], $ROUND);
    $r;
}

sub _mpz2mpc {
    my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
    Math::MPC::Rmpc_set_z($r, $_[0], $ROUND);
    $r;
}

#
## MPQ
#
sub _mpq2mpz {
    my $z = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_set_q($z, $_[0]);
    $z;
}

sub _mpq2mpfr {
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set_q($r, $_[0], $ROUND);
    $r;
}

sub _mpq2mpc {
    my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
    Math::MPC::Rmpc_set_q($r, $_[0], $ROUND);
    $r;
}

#
## MPFR
#
sub _mpfr2mpc {
    my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
    Math::MPC::Rmpc_set_fr($r, $_[0], $ROUND);
    $r;
}

#
## Any to MPC (complex)
#
sub _any2mpc {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    if (!ref($x)) {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        if (FAST_MODE and $x < ULONG_MAX and $x > LONG_MIN) {
            ($x < 0)
              ? Math::MPC::Rmpc_set_si($r, $x, $ROUND)
              : Math::MPC::Rmpc_set_ui($r, $x, $ROUND);
        }
        else {
            Math::MPC::Rmpc_set_str($r, $x, 10, $ROUND);
        }
        return $r;
    }

    ref($x) eq 'Math::MPC'  && return $x;
    ref($x) eq 'Math::GMPz' && goto &_mpz2mpc;
    ref($x) eq 'Math::GMPq' && goto &_mpq2mpc;

    goto &_mpfr2mpc;
}

#
## Any to MPFR (floating-point)
#
sub _any2mpfr {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    if (!ref($x)) {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        if (FAST_MODE and $x < ULONG_MAX and $x > LONG_MIN) {
            ($x < 0)
              ? Math::MPFR::Rmpfr_set_si($r, $x, $ROUND)
              : Math::MPFR::Rmpfr_set_ui($r, $x, $ROUND);
        }
        else {
            Math::MPFR::Rmpfr_set_str($r, $x, 10, $ROUND);
        }
        return $r;
    }

    ref($x) eq 'Math::MPFR' && return $x;
    ref($x) eq 'Math::GMPz' && goto &_mpz2mpfr;
    ref($x) eq 'Math::GMPq' && goto &_mpq2mpfr;

    my $fr = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPC::RMPC_IM($fr, $x);

    if (Math::MPFR::Rmpfr_zero_p($fr)) {
        Math::MPC::RMPC_RE($fr, $x);
    }
    else {
        Math::MPFR::Rmpfr_set_nan($fr);
    }

    $fr;
}

#
## Any to MPFR or MPC, in this order
#
sub _any2mpfr_mpc {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    if (   ref($x) eq 'Math::MPFR'
        or ref($x) eq 'Math::MPC') {
        return $x;
    }

    ref($x) eq 'Math::GMPz' && goto &_mpz2mpfr;
    ref($x) eq 'Math::GMPq' && goto &_mpq2mpfr;
    goto &_any2mpfr;
}

#
## Any to GMPz (integer)
#
sub _any2mpz {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    if (!ref($x)) {

        return (
                (FAST_MODE and $x < ULONG_MAX and $x > LONG_MIN)
                ? (
                   ($x < 0)
                   ? Math::GMPz::Rmpz_init_set_si($x)
                   : Math::GMPz::Rmpz_init_set_ui($x)
                  )
                : Math::GMPz::Rmpz_init_set_str($x, 10)
               );
    }

    ref($x) eq 'Math::GMPz' && return $x;
    ref($x) eq 'Math::GMPq' && goto &_mpq2mpz;

    if (ref($x) eq 'Math::MPFR') {
        if (Math::MPFR::Rmpfr_number_p($x)) {
            my $z = Math::GMPz::Rmpz_init();
            Math::MPFR::Rmpfr_get_z($z, $x, $round_z);
            return $z;
        }
        return;
    }

    (@_) = _any2mpfr($x);
    goto &_any2mpz;
}

#
## Any to GMPq (rational)
#
sub _any2mpq {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    if (!ref($x)) {
        my $q = Math::GMPq::Rmpq_init();
        if (FAST_MODE and $x < ULONG_MAX and $x > LONG_MIN) {
            ($x < 0)
              ? Math::GMPq::Rmpq_set_si($q, $x, 1)
              : Math::GMPq::Rmpq_set_ui($q, $x, 1);
        }
        else {
            Math::GMPq::Rmpq_set_str($q, $x, 10);
        }
        return $q;
    }

    ref($x) eq 'Math::GMPq' && return $x;
    ref($x) eq 'Math::GMPz' && goto &_mpz2mpq;

    if (ref($x) eq 'Math::MPFR') {
        if (Math::MPFR::Rmpfr_number_p($x)) {
            my $q = Math::GMPq::Rmpq_init();
            Math::MPFR::Rmpfr_get_q($q, $x);
            return $q;
        }
        return;
    }

    (@_) = _any2mpfr($x);
    goto &_any2mpq;
}

sub _fits_ulong {
    (!ref($_[0]))
      ? ($_[0] >= 0)
      : (ref($_[0]) eq 'Math::GMPz' and Math::GMPz::Rmpz_fits_ulong_p($_[0]));
}

sub _fits_slong {
    (!ref($_[0]))
      ? 1
      : (ref($_[0]) eq 'Math::GMPz' and Math::GMPz::Rmpz_fits_slong_p($_[0]));
}

sub _get_ulong {
    (!ref($_[0]))
      ? $_[0]
      : Math::GMPz::Rmpz_get_ui($_[0]);
}

sub _get_slong {
    (!ref($_[0]))
      ? $_[0]
      : Math::GMPz::Rmpz_get_si($_[0]);
}

#
## Anything to an unsigned native integer
#
sub _any2ui {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        return (($x < 0) ? undef : $x);
    }

  Math_GMPz: {

        if (Math::GMPz::Rmpz_fits_ulong_p($x)) {
            return Math::GMPz::Rmpz_get_ui($x);
        }

        state $t = Math::GMPz::Rmpz_init_set_str_nobless(join('', ~0), 10);

        if (Math::GMPz::Rmpz_sgn($x) >= 0 and Math::GMPz::Rmpz_cmp($x, $t) <= 0) {
            return Math::GMPz::Rmpz_get_str($x, 10);
        }

        return;
    }

  Math_GMPq: {

        if (Math::GMPq::Rmpq_integer_p($x)) {
            @_ = ($x = _mpq2mpz($x));
            goto Math_GMPz;
        }

        my $d = CORE::int(Math::GMPq::Rmpq_get_d($x));
        return (($d < 0 or $d > ULONG_MAX) ? undef : $d);
    }

  Math_MPFR: {

        if (Math::MPFR::Rmpfr_integer_p($x) and Math::MPFR::Rmpfr_fits_ulong_p($x, $ROUND)) {
            return Math::MPFR::Rmpfr_get_ui($x, $ROUND);
        }

        if (Math::MPFR::Rmpfr_number_p($x)) {
            my $d = CORE::int(Math::MPFR::Rmpfr_get_d($x, $ROUND));
            return (($d < 0 or $d > ULONG_MAX) ? undef : $d);
        }

        return;
    }

  Math_MPC: {
        @_ = ($x = _any2mpfr($x));
        goto Math_MPFR;
    }
}

#
## Anything to a signed native integer
#
sub _any2si {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;

    goto($DISPATCH_TAG{ref($x) || return $x});

  Math_GMPz: {

        if (Math::GMPz::Rmpz_fits_slong_p($x)) {
            return Math::GMPz::Rmpz_get_si($x);
        }

        if (Math::GMPz::Rmpz_fits_ulong_p($x)) {
            return Math::GMPz::Rmpz_get_ui($x);
        }

        return;
    }

  Math_GMPq: {

        if (Math::GMPq::Rmpq_integer_p($x)) {
            @_ = ($x = _mpq2mpz($x));
            goto Math_GMPz;
        }

        my $d = CORE::int(Math::GMPq::Rmpq_get_d($x));
        return (($d < LONG_MIN or $d > ULONG_MAX) ? undef : $d);
    }

  Math_MPFR: {

        if (Math::MPFR::Rmpfr_integer_p($x)) {
            if (Math::MPFR::Rmpfr_fits_slong_p($x, $ROUND)) {
                return Math::MPFR::Rmpfr_get_si($x, $ROUND);
            }

            if (Math::MPFR::Rmpfr_fits_ulong_p($x, $ROUND)) {
                return Math::MPFR::Rmpfr_get_ui($x, $ROUND);
            }
        }

        if (Math::MPFR::Rmpfr_number_p($x)) {
            my $d = CORE::int(Math::MPFR::Rmpfr_get_d($x, $ROUND));
            return (($d < LONG_MIN or $d > ULONG_MAX) ? undef : $d);
        }

        return;
    }

  Math_MPC: {
        @_ = ($x = _any2mpfr($x));
        goto Math_MPFR;
    }
}

# Big to (signed) integer-string
sub _big2istr {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;
    ref($x) || return $x;

    # die ((caller(1))[3], ' -- ', (caller(0))[2]) if index(ref($x), 'Sidef::') == 0;

    $x = (_any2mpz($x) // return undef) if ref($x) ne 'Math::GMPz';

    Math::GMPz::Rmpz_fits_slong_p($x)
      ? Math::GMPz::Rmpz_get_si($x)
      : Math::GMPz::Rmpz_get_str($x, 10);
}

# Big to unsigned (non-negative) integer-string
sub _big2uistr {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;
    ref($x) || return (($x >= 0) ? $x : undef);

    # die ((caller(1))[3], ' -- ', (caller(0))[2]) if index(ref($x), 'Sidef::') == 0;

    $x = (_any2mpz($x) // return undef) if ref($x) ne 'Math::GMPz';

    Math::GMPz::Rmpz_fits_ulong_p($x)
      ? Math::GMPz::Rmpz_get_ui($x)
      : ((Math::GMPz::Rmpz_sgn($x) >= 0) ? Math::GMPz::Rmpz_get_str($x, 10) : undef);
}

# Big to positive integer-string
sub _big2pistr {
    my ($x) = @_;

    $x = $$x if ref($x) eq __PACKAGE__;
    ref($x) || return (($x > 0) ? $x : undef);

    # die ((caller(1))[3], ' -- ', (caller(0))[2]) if index(ref($x), 'Sidef::') == 0;

    $x = (_any2mpz($x) // return undef) if ref($x) ne 'Math::GMPz';

    if (Math::GMPz::Rmpz_fits_ulong_p($x)) {
        my $ui = Math::GMPz::Rmpz_get_ui($x);
        return (($ui > 0) ? $ui : undef);
    }

    (Math::GMPz::Rmpz_sgn($x) > 0)
      ? Math::GMPz::Rmpz_get_str($x, 10)
      : undef;
}

sub _array {
    Sidef::Types::Array::Array->new(@_);
}

sub _sanitize_mpz {
    my ($n) = @_;

    if (ref($n) ne 'Math::GMPz') {
        __is_int__($n) || return undef;
        $n = _any2mpz($n) // return undef;
    }

    $n;
}

sub _normalize_numeric_type {
    my ($n) = @_;
    ref($n) eq 'Math::GMPz'
      ? (
           Math::GMPz::Rmpz_fits_ulong_p($n)
         ? Math::GMPz::Rmpz_get_ui($n)
         : Math::GMPz::Rmpz_get_str($n, 10)
        )
      : $n;
}

sub _execute_pari_gp {
    my ($code) = @_;
    say STDERR ":: Executing PARI/GP with: $code" if $VERBOSE;

    state $_x = require IPC::Open2;

    # Maintain persistent handles for the lifetime of the interpreter
    state ($gp_in, $gp_out, $gp_pid);

    state $gp_cmd = 'gp -q -f --default parisize=500M --default parisizemax=0 --default threadsizemax=0';

    # Lazy initialization: only spawn the process on the first call
    if (!$gp_pid) {
        $gp_pid = IPC::Open2::open2($gp_out, $gp_in, $gp_cmd);

        # Disable buffering on the input handle to ensure GP gets commands immediately
        my $old_fh = select($gp_in);
        $| = 1;
        select($old_fh);
    }

    # Unique sentinel to mark the end of the GP output stream
    my $sentinel = "---END_OF_OUTPUT---";

    # Protect against script crashes if the GP process was killed externally
    local $SIG{PIPE} = 'IGNORE';

    # Send the code.
    # The sentinel is sent on a new line so that even if $code triggers
    # a GP syntax error, the sentinel is still evaluated and prevents a deadlock.
    my $write_ok = print $gp_in "$code\nprint(\"$sentinel\");\n";

    # Handle GP process death and auto-restart
    if (!$write_ok) {
        say STDERR "[WARNING] PARI/GP pipe broke, restarting..." if $VERBOSE;
        close $gp_in                                             if $gp_in;
        close $gp_out                                            if $gp_out;
        undef $gp_pid;

        # Re-initialize and retry exactly once
        $gp_pid = IPC::Open2::open2($gp_out, $gp_in, $gp_cmd);
        my $old_fh = select($gp_in);
        $| = 1;
        select($old_fh);

        print $gp_in "$code\nprint(\"$sentinel\");\n" or return undef;
    }

    my @output;
    while (my $line = <$gp_out>) {
        chomp $line;
        last if $line eq $sentinel;
        push @output, $line;
    }

    # GP errors are printed to STDERR natively, bypassing @output.
    # If $code fails or returns nothing, @output will be safely empty.
    my $res = join("\n", @output);
    return undef if $res eq '';
    return $res;
}

sub _execute_in_tmpdir {
    my ($cmd) = @_;

    my $cwd = Sidef::Types::Glob::Dir->cwd;

    # The directory is deleted when the object goes out of scope.
    require File::Temp;
    my $tmp = File::Temp->newdir(CLEANUP => 1);

    CORE::chdir($tmp);

    my $output    = `$cmd`;
    my $exit_code = $?;

    $cwd->chdir;

    return ($output, $exit_code);
}

sub _is_prob_prime {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    if ($n < ULONG_MAX) {
        return HAS_PRIME_UTIL
          ? Math::Prime::Util::is_prime($n)
          : Math::Prime::Util::GMP::is_prime($n);
    }

    if (exists($IS_PRIME_CACHE{$n})) {
        return $IS_PRIME_CACHE{$n};
    }

    my $r = (
        (CORE::length($n) > PRIMALITY_PRETEST_MIN)
        ? do {
            state $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_set_str($t, "$n", 10);
            _primality_pretest($t);
          }
        : 1
      )
      && Math::Prime::Util::GMP::is_prime($n);

    # FIFO Cache management
    if (@IS_PRIME_QUEUE >= IS_PRIME_CACHE_SIZE) {
        delete $IS_PRIME_CACHE{shift @IS_PRIME_QUEUE};
    }

    push @IS_PRIME_QUEUE, $n;
    $IS_PRIME_CACHE{$n} = $r;
    $r;
}

# --- Helper Methods ---

sub _sort_gmp_factors {
    return map { $_->[0] }
      sort { Math::GMPz::Rmpz_cmp($a->[1], $b->[1]) }
      map { [$_, Math::GMPz::Rmpz_init_set_str($_, 10)] } @_;
}

sub _factor_via_prime_util {
    my ($n) = @_;
    (HAS_PRIME_UTIL and $n < ULONG_MAX)
      ? Math::Prime::Util::factor($n)
      : Math::Prime::Util::GMP::factor($n);
}

# --- Specific Factorization Strategies ---

sub _factor_special {
    my ($n) = @_;
    local $SPECIAL_FACTORS = 0;
    say STDERR "Looking for special factors: $n" if $VERBOSE;

    my @special_factors;
    my $composite_factors = 0;
    my $limit             = (CORE::length($n) <= YAFU_MIN) ? ZERO : ();

    foreach my $p (@{_set_int($n)->special_factors($limit)}) {
        if (_is_prob_prime($$p)) {
            push @special_factors, "$$p";
        }
        else {
            say STDERR "Recursively factoring: $$p" if $VERBOSE;
            $composite_factors = 1;
            push @special_factors, _factor($$p);
        }
    }

    return () unless @special_factors;
    return $composite_factors ? _sort_gmp_factors(@special_factors) : @special_factors;
}

sub _factor_factordb {
    my ($n) = @_;
    say STDERR "FactorDB: factoring $n" if $VERBOSE;

    require HTTP::Tiny;
    my $http     = HTTP::Tiny->new;
    my $response = $http->get("http://factordb.com/api?query=$n");

    return () unless $response->{success};

    my $data = eval { require JSON::PP; JSON::PP::decode_json($response->{content}) };
    return () unless (defined($data) && ref($data) eq 'HASH' && ref($data->{factors}) eq 'ARRAY');

    my @factordb_factors;
    my @unknown_factors;

    foreach my $pp (@{$data->{factors}}) {
        my ($p, $e) = @$pp;

        if (_is_prob_prime($p)) {
            push @factordb_factors, ($p) x Math::Prime::Util::GMP::valuation($n, $p);
        }
        else {
            say STDERR "FactorDB: composite factor $p" if $VERBOSE;
            local $USE_FACTORDB = 0;
            my @arr = _factor($p);
            push @unknown_factors, @arr;
            push @factordb_factors, (@arr) x Math::Prime::Util::GMP::valuation($n, $p);
        }
    }

    if (@unknown_factors) {
        say STDERR "FactorDB: sending new factors to factordb.com" if $VERBOSE;
        my $resp = $http->post_form("http://factordb.com/report.php", {msub => "$n = " . join(' * ', @unknown_factors)});
        if ($VERBOSE) {
            say STDERR (
                        $resp->{success}
                        ? (
                           $resp->{content} =~ /Thank you/
                           ? "FactorDB: thank you for your contribution!"
                           : "FactorDB: sent factors successfully!"
                          )
                        : "FactorDB: failed to send the new factors..."
                       );
        }
    }

    my $factors_prod = Math::Prime::Util::GMP::vecprod(@factordb_factors);

    if ($factors_prod ne $n and Math::Prime::Util::GMP::modint($n, $factors_prod) eq '0') {
        say STDERR "FactorDB: recursively factoring the remainder." if $VERBOSE;
        my $r = Math::Prime::Util::GMP::divint($n, $factors_prod);
        local $USE_FACTORDB = 0;
        push @factordb_factors, _factor($r);
        $factors_prod = Math::Prime::Util::GMP::mulint($factors_prod, $r);
    }

    if ($factors_prod eq $n) {
        say STDERR "FactorDB: successful factorization." if $VERBOSE;
        return _sort_gmp_factors(@factordb_factors);
    }

    return ();
}

sub _factor_yafu {
    my ($n) = @_;
    say STDERR "YAFU: factoring $n" if $VERBOSE;

    my ($yafu_output, $exit_code) = _execute_in_tmpdir("yafu $n");
    return () unless ($exit_code == 0 and defined($yafu_output));

    my @yafu_factors;
    while ($yafu_output =~ /^([PC])\d+\s*=\s*([0-9]+)/gm) {
        my ($status, $factor) = ($1, $2);
        if ($status eq 'P') {
            push @yafu_factors, $factor;
        }
        else {
            local $USE_YAFU = 0;
            push @yafu_factors, _factor($factor);
        }
    }

    # Ensure all factors are prime, otherwise recurse
    if (grep { !_is_prob_prime($_) } List::Util::uniq(@yafu_factors)) {
        say STDERR "YAFU: not all factors are prime." if $VERBOSE;
        local $USE_YAFU = 0;
        @yafu_factors = map { _factor($_) } @yafu_factors;
    }

    my $factors_prod = Math::Prime::Util::GMP::vecprod(@yafu_factors);

    if ($factors_prod ne $n) {
        say STDERR "YAFU: factors do not multiply to n." if $VERBOSE;
        if (Math::Prime::Util::GMP::modint($n, $factors_prod) eq '0') {
            say STDERR "YAFU: recomputing multiplicities..." if $VERBOSE;

            my @corrected_factors;
            foreach my $p (List::Util::uniq(@yafu_factors)) {
                next if $n eq $p;
                my $v = Math::Prime::Util::GMP::valuation($n, $p);
                push(@corrected_factors, ($p) x $v) if $v > 0;
            }

            @yafu_factors = @corrected_factors;
            $factors_prod = Math::Prime::Util::GMP::vecprod(@yafu_factors);

            if ($factors_prod ne $n and Math::Prime::Util::GMP::modint($n, $factors_prod) eq '0') {
                say STDERR "YAFU: recursively factoring the remainder." if $VERBOSE;
                my $r = Math::Prime::Util::GMP::divint($n, $factors_prod);
                local $USE_YAFU = 0;
                push @yafu_factors, _factor($r);
                $factors_prod = Math::Prime::Util::GMP::mulint($factors_prod, $r);
            }
        }
        else {
            say STDERR "YAFU: product of factors do not divide n." if $VERBOSE;
        }
    }

    if ($factors_prod eq $n) {
        say STDERR "YAFU: successful factorization: @yafu_factors" if $VERBOSE;
        return _sort_gmp_factors(@yafu_factors);
    }

    return ();
}

sub _factor_pari {
    my ($n) = @_;

    # Extract flat space-separated factors from the PARI factorization matrix
    my $code = "iferr(my(f=factorint($n)); for(i=1, matsize(f)[1], for(j=1, f[i,2], print1(f[i,1], \" \"))), E, \"\")";
    if (my $res = _execute_pari_gp($code)) {
        my @pari_factors = split(' ', $res);
        if (@pari_factors) {
            say STDERR "PARI/GP factorint(r): @pari_factors" if $VERBOSE;
            return @pari_factors;
        }
    }

    return ();
}

sub _factor_generic {
    my ($n) = @_;

    my $orig_n = $n;
    my @factors;

    # Adaptive trial factorization for exceptionally large numbers
    if (CORE::length($n) > 500) {
        ($n, @factors) = _adaptive_trial_factor($n);

        if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
            return \@factors if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;
            $n = Math::GMPz::Rmpz_get_ui($n);
            push @factors, _factor_via_prime_util($n);
            return \@factors;
        }
        else {
            $n = Math::GMPz::Rmpz_get_str($n, 10);
        }
    }

    my $len = CORE::length($n);
    if ($len >= SPECIAL_FACTORS_MIN and _is_prob_prime($n)) {
        push @factors, $n;
        return \@factors;
    }

    # 1. Special Factors Strategy
    if ($len >= SPECIAL_FACTORS_MIN and $SPECIAL_FACTORS) {

        # Throw away the small factors (if any)
        my @special = _factor_special($orig_n);
        return \@special if @special;
    }

    # 2. FactorDB Strategy
    if ($len >= FACTORDB_MIN and $USE_FACTORDB) {
        my @fdb = _factor_factordb($n);
        if (@fdb) {
            say STDERR "FactorDB: Success!" if $VERBOSE;
            push @factors, @fdb;
            return \@factors;
        }
        else {
            say STDERR "FactorDB: Failed!" if $VERBOSE;
        }
    }

    # 3. YAFU Strategy
    if ($len >= YAFU_MIN and $USE_YAFU and -t STDIN) {
        my @yafu = _factor_yafu($n);
        if (@yafu) {
            say STDERR "YAFU: Success!" if $VERBOSE;
            push @factors, @yafu;
            return \@factors;
        }
        else {
            say STDERR "YAFU: Failed!" if $VERBOSE;
        }
    }

    # 4. PARI Strategy
    if ($len >= PARI_GP_MIN and $USE_PARI_GP) {
        my @pari = _factor_pari($n);
        if (@pari) {
            say STDERR "PARI/GP: Success!" if $VERBOSE;
            push @factors, @pari;
            return \@factors;
        }
        else {
            say STDERR "PARI/GP: Failed!" if $VERBOSE;
        }
    }

    # 5. Fallback Strategy
    push @factors, _factor_via_prime_util($n);
    return \@factors;
}

sub _factor {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    # Fast path for native integers
    return _factor_via_prime_util($n) if (FAST_MODE and $n < ULONG_MAX);

    # Fast cache return
    if (my $cached = $FACTOR_CACHE{$n}) {
        return @$cached;
    }

    my $factors = _factor_generic($n);

    # FIFO Cache management
    if (@FACTOR_QUEUE >= FACTOR_CACHE_SIZE) {
        delete $FACTOR_CACHE{shift @FACTOR_QUEUE};
    }
    push @FACTOR_QUEUE, $n;
    $FACTOR_CACHE{$n} = $factors;

    return @$factors;
}

# Prime factorization in [p,k] form, where k is the multiplicity of p.
sub _factor_exp {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
        return Math::Prime::Util::factor_exp($n);
    }

    if (my $cached = $FACTOR_EXP_CACHE{$n}) {
        return @$cached;
    }

    my @factors    = _factor($n);
    my $prev_value = shift(@factors) // return;

    my $current_pair = [$prev_value, 1];
    my @factor_exp   = ($current_pair);

    foreach my $curr_value (@factors) {
        if ($curr_value eq $prev_value) {
            $current_pair->[1]++;
        }
        else {
            $current_pair = [$curr_value, 1];
            push(@factor_exp, $current_pair);
            $prev_value = $curr_value;
        }
    }

    # FIFO Cache management
    if (@FACTOR_EXP_QUEUE >= FACTOR_EXP_CACHE_SIZE) {
        delete $FACTOR_EXP_CACHE{shift @FACTOR_EXP_QUEUE};
    }
    push @FACTOR_EXP_QUEUE, $n;
    $FACTOR_EXP_CACHE{$n} = \@factor_exp;

    return @factor_exp;
}

sub _divisors {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    if (FAST_MODE and $n < ULONG_MAX) {
        return HAS_PRIME_UTIL
          ? Math::Prime::Util::divisors($n)
          : Math::Prime::Util::GMP::divisors($n);
    }

    my @divisors;

    if (my $cached = $DIVISORS_CACHE{$n}) {
        return @$cached;
    }

    if (CORE::length($n) >= SPECIAL_FACTORS_MIN) {
        my $t = _set_int($n);
        my $D = $t->divisors($t);
        @divisors = map { (ref($$_) eq 'Math::GMPz') ? Math::GMPz::Rmpz_get_str($$_, 10) : $$_ } @$D;
    }
    else {
        @divisors = Math::Prime::Util::GMP::divisors($n);
    }

    # FIFO Cache management
    if (@DIVISORS_QUEUE >= DIVISORS_CACHE_SIZE) {
        delete $DIVISORS_CACHE{shift @DIVISORS_QUEUE};
    }

    push @DIVISORS_QUEUE, $n;
    $DIVISORS_CACHE{$n} = \@divisors;

    return @divisors;
}

sub _primes {
    my ($lo, $hi) = @_;

    if (HAS_PRIME_UTIL and $hi < ULONG_MAX) {
        return Math::Prime::Util::primes($lo, $hi);
    }

    [Math::Prime::Util::GMP::sieve_primes("$lo", "$hi")];
}

sub _cached_pn_primorial {
    my ($k) = @_;
    state @pn_primorial;
    $pn_primorial[$k] //= Math::GMPz::Rmpz_init_set_str_nobless(Math::Prime::Util::GMP::pn_primorial($k), 10);
}

sub _cached_primorial {
    my ($k, $limit) = @_;
    $limit //= 100;

    state %primorial_cache;
    state %access_count;

    if (exists $primorial_cache{$k}) {
        $access_count{$k}++;
        return $primorial_cache{$k};
    }

    my $t;

    if (scalar(keys(%primorial_cache)) >= $limit) {

        # Evict least-used entry
        my $min_key = (
                       sort { $a->[1] <=> $b->[1] }
                       map  { [$_, $access_count{$_}] } keys %primorial_cache
                      )[0][0];

        $t = delete $primorial_cache{$min_key};
        delete $access_count{$min_key};
    }
    else {
        $t = Math::GMPz::Rmpz_init_nobless();
    }

    # Compute and cache
    say STDERR "Computing primorial($k)..." if ($k >= 1e6 and $VERBOSE);
    Math::GMPz::Rmpz_primorial_ui($t, $k);
    $primorial_cache{$k} = $t;
    $access_count{$k}    = 1;

    return $t;
}

sub _is_squarefree {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    if (CORE::length($n) >= SPECIAL_FACTORS_MIN) {
        foreach my $pp (_factor_exp($n)) {
            if ($pp->[1] > 1) {
                return;
            }
        }
        return 1;
    }

    (HAS_PRIME_UTIL and $n < ULONG_MAX)
      ? Math::Prime::Util::is_square_free($n)
      : (Math::Prime::Util::GMP::moebius($n) != 0);
}

sub _next_prime {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    (HAS_PRIME_UTIL and $n < (ULONG_MAX - 2000))
      ? Math::Prime::Util::next_prime($n)
      : Math::Prime::Util::GMP::next_prime($n);
}

sub _prev_prime {
    my ($n) = @_;

    $n = _normalize_numeric_type($n) if ref($n);

    (HAS_PRIME_UTIL and $n < ULONG_MAX)
      ? Math::Prime::Util::prev_prime($n)
      : Math::Prime::Util::GMP::prev_prime($n);
}

sub _random_prime {
    my ($n) = @_;

    (HAS_PRIME_UTIL and $n < ULONG_MAX)
      ? Math::Prime::Util::random_prime($n)
      : Math::Prime::Util::GMP::random_prime($n);
}

sub _primorial_trial_factor {
    my ($n, $k) = @_;

    # n is a positive > 1 Math::GMPz object
    # k is an unsigned integer

    my $B = _cached_primorial($k);

    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($g, $n, $B);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

        my $r = Math::GMPz::Rmpz_init_set($n);

        local $USE_YAFU        = 0;
        local $USE_FACTORDB    = 0;
        local $SPECIAL_FACTORS = 0;

        my @factors = _factor(Math::GMPz::Rmpz_get_str($g, 10));
        my @prime_factors;

        foreach my $f (@factors) {
            Math::GMPz::Rmpz_set_ui($g, $f);
            push @prime_factors, ($f) x Math::GMPz::Rmpz_remove($r, $r, $g);
        }

        return ($r, @prime_factors);
    }

    return Math::GMPz::Rmpz_init_set($n);
}

sub _adaptive_trial_factor {
    my ($n, $F, $L, $R) = @_;

    $F //= 2;
    $L //= 1e6;
    $R //= 2e6;

    if (ref($n) eq 'Math::GMPz') {
        $n = Math::GMPz::Rmpz_init_set($n);    # copy
    }
    else {
        $n = Math::GMPz::Rmpz_init_set_str($n, 10);
    }

    my @factors;

    my $P = _cached_primorial($L);

    state $g = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    while (1) {

        Math::GMPz::Rmpz_gcd($g, $P, $n);

        # Early stop when n seems to no longer have small factors
        if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
            last;
        }

        foreach my $p (@{_primes($F, $L)}) {
            if (Math::GMPz::Rmpz_divisible_ui_p($g, $p)) {

                Math::GMPz::Rmpz_set_ui($t, $p);
                push @factors, ($p) x Math::GMPz::Rmpz_remove($n, $n, $t);

                # Stop the loop early when no more primes divide `g` (optional)
                Math::GMPz::Rmpz_divexact_ui($g, $g, $p);
                last if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0);
            }
        }

        # Early stop when n has been fully factored or the trial range has been exhausted
        if ($L >= $R or Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
            last;
        }

        $F = $L;
        $L <<= 1;
        $P = _cached_primorial($L);
    }

    return ($n, @factors);
}

#
## Binary splitting
#

sub _binsplit {
    my ($arr, $func) = @_;

    while ($#$arr > 0) {
        my $x = CORE::shift(@$arr);
        my $y = CORE::shift(@$arr);
        push(@$arr, $func->($x, $y));
    }

    $arr->[0];
}

#
## Generic each
#

sub _generic_each {
    my ($from, $to, $block, $step_function, $buffer_callback) = @_;

    # `from` and `to` are Math::GMPz objects
    # `block` is a Sidef callback block
    # `step_function` is a Perl subroutine to compute the step given the initial current value
    # `buffer_callback` is a Perl subroutine that returns an ARRAY ref with the values in the given range

    if (Math::GMPz::Rmpz_cmp($from, $to) > 0) {
        return ZERO;
    }

    my @buffer;
    my $done  = 0;
    my $count = 0;

    for (; ;) {

        if (!@buffer) {

            last if $done;

            my $step = $step_function->($from);

            if ($step <= 0) {
                $step = 1e6;
            }

            my $upto = $from + $step;

            if ($upto >= $to) {
                $done = 1;
                $upto = $to;
            }

            #~ say ":: Sieving ($from, $upto) with step = $step";
            @buffer = @{$buffer_callback->($from, $upto) // die "[ERROR] Failed to sieve the range [$from, $upto]"};
            $from   = $upto + 1;
            @buffer || next;
        }

        ++$count;
        $block->run(_set_int(shift(@buffer)));
    }

    _set_int($count);
}

#
## Internal conversion methods
#

sub __boolify__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return !!$x});

  Math_MPFR: {
        return !!Math::MPFR::Rmpfr_sgn($x);
    }

  Math_GMPq: {
        return !!Math::GMPq::Rmpq_sgn($x);
    }

  Math_GMPz: {
        return !!Math::GMPz::Rmpz_sgn($x);
    }

  Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_RE($r, $x);
        Math::MPFR::Rmpfr_sgn($r)   && return 1;
        Math::MPFR::Rmpfr_nan_p($r) && return 0;
        Math::MPC::RMPC_IM($r, $x);
        return !!Math::MPFR::Rmpfr_sgn($r);
    }
}

sub __numify__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return $x});

  Math_GMPz: {

        if (Math::GMPz::Rmpz_fits_slong_p($x)) {
            return Math::GMPz::Rmpz_get_si($x);
        }

        if (Math::GMPz::Rmpz_fits_ulong_p($x)) {
            return Math::GMPz::Rmpz_get_ui($x);
        }

        return Math::GMPz::Rmpz_get_d($x);
    }

  Math_GMPq: {

        if (Math::GMPq::Rmpq_integer_p($x)) {
            $x = _mpq2mpz($x);
            goto Math_GMPz;
        }

        return Math::GMPq::Rmpq_get_d($x);
    }

  Math_MPFR: {
        if (Math::MPFR::Rmpfr_integer_p($x)) {
            if (Math::MPFR::Rmpfr_fits_slong_p($x, $ROUND)) {
                return Math::MPFR::Rmpfr_get_si($x, $ROUND);
            }

            if (Math::MPFR::Rmpfr_fits_ulong_p($x, $ROUND)) {
                return Math::MPFR::Rmpfr_get_ui($x, $ROUND);
            }
        }

        return Math::MPFR::Rmpfr_get_d($x, $ROUND);
    }

  Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_RE($r, $x);
        $x = $r;
        goto Math_MPFR;
    }
}

sub numify {
    (@_) = (${$_[0]});
    goto &__numify__;
}

sub __stringify__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return $x});

  Math_GMPz: {
        return Math::GMPz::Rmpz_get_str($x, 10);
    }

  Math_GMPq: {

        #return Math::GMPq::Rmpq_get_str($x, 10);

        Math::GMPq::Rmpq_integer_p($x)
          && return Math::GMPq::Rmpq_get_str($x, 10);

        $PREC = CORE::int($PREC) if ref($PREC);

        state $z = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_q($z, $x);

        my $size = Math::GMPz::Rmpz_sizeinbase($z, 10) - 1;

        my $f = Math::MPFR::Rmpfr_init2(CORE::int(($size + $PREC / 4) * CORE::log(10) / CORE::log(2)) + 10);
        Math::MPFR::Rmpfr_set_q($f, $x, $ROUND);

        local $PREC = 4 * $size + $PREC;
        return __SUB__->($f);
    }

  Math_MPFR: {
        Math::MPFR::Rmpfr_number_p($x)
          || return (
                       Math::MPFR::Rmpfr_nan_p($x)   ? 'NaN'
                     : Math::MPFR::Rmpfr_sgn($x) < 0 ? '-Inf'
                     :                                 'Inf'
                    );

        # log(10)/log(2) =~ 3.3219280948873623
        my $digits = CORE::int($PREC) >> 2;
        my ($mantissa, $exponent) = Math::MPFR::Rmpfr_deref2($x, 10, $digits, $ROUND);

        my $sgn = '';
        if (substr($mantissa, 0, 1) eq '-') {
            $sgn = substr($mantissa, 0, 1, '');
        }

        $mantissa == 0 and return '0';

        if (CORE::abs($exponent) < CORE::length($mantissa)) {

            if ($exponent > 0) {
                substr($mantissa, $exponent, 0, '.');
            }
            else {
                substr($mantissa, 0, 0, '0.' . ('0' x CORE::abs($exponent)));
            }

            $mantissa = CORE::reverse($mantissa);
            $mantissa =~ s/^0+//;
            $mantissa =~ s/^\.//;
            $mantissa = CORE::reverse($mantissa);

            return ($sgn . $mantissa);
        }

        if (CORE::length($mantissa) > 1) {
            substr($mantissa, 1, 0, '.');
        }

        return ($sgn . $mantissa . 'e' . ($exponent - 1));
    }

  Math_MPC: {
        my $fr = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($fr, $x);
        my $re = __SUB__->($fr);

        Math::MPC::RMPC_IM($fr, $x);
        my $im = __SUB__->($fr);

        if ($im eq '0' or $im eq '-0') {
            return $re;
        }

        my $sign = '+';

        if (substr($im, 0, 1) eq '-') {
            $sign = '-';
            substr($im, 0, 1, '');
        }

        #$im = '' if $im eq '1';
        return ($re eq '0' ? $sign eq '+' ? "${im}i" : "$sign${im}i" : "$re $sign ${im}i");
    }
}

sub get_value {
    my ($x) = @_;

    $x = $$x;

    ref($x) || return $x;    # native integer

    # Convert fraction to a floating-point value, so it can be passed to Perl modules
    if (ref($x) eq 'Math::GMPq' and !Math::GMPq::Rmpq_integer_p($x)) {
        $x = _mpq2mpfr($x);
    }

    __stringify__($x);
}

#
## Public conversion methods
#

sub int {
    my ($x) = @_;
    (!ref($$x) or ref($$x) eq 'Math::GMPz') ? $x : do {
        my $t = _any2mpz($$x) // (goto &nan);
        $t = Math::GMPz::Rmpz_get_si($t) if Math::GMPz::Rmpz_fits_slong_p($t);
        bless \$t;
    };
}

*trunc  = \&int;
*to_i   = \&int;
*to_int = \&int;

sub rat {
    my ($x) = @_;
    ref($$x) eq 'Math::GMPq' ? $x : bless \(_any2mpq($$x) // (goto &nan));
}

*to_r   = \&rat;
*to_rat = \&rat;

sub float {
    my ($x) = @_;
    (ref($$x) eq 'Math::MPFR' || ref($$x) eq 'Math::MPC') ? $x : bless \_any2mpfr_mpc($$x);
}

*f        = \&float;
*to_f     = \&float;
*to_float = \&float;

sub to_poly {
    my ($x) = @_;
    Sidef::Types::Number::Polynomial->new(0 => $x);
}

sub eval {
    $_[0];
}

sub lift {
    $_[0];
}

sub complex {
    my ($x, $y) = @_;

    if (defined $y) {
        return Sidef::Types::Number::Complex->new($x, $y);
    }

    ref($$x) eq 'Math::MPC' ? $x : bless \_any2mpc($$x);
}

sub rat_approx {
    my ($x_ref, $max_den) = @_;

    # Algorithm from Math::Prime::Util

    if (defined($max_den)) {
        $max_den = _any2mpz($$max_den) // goto &nan;
    }

    my $x = _any2mpfr($$x_ref);
    Math::MPFR::Rmpfr_number_p($x) || goto &nan;

    # 1. Count decimal places
    if (!defined($max_den)) {
        my $input_str      = __stringify__($x);
        my $decimal_places = (length($input_str) - index($input_str, '.'));
        $max_den = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_ui_pow_ui($max_den, 10, $decimal_places >> 1);
    }

    # Extract sign and work strictly with absolute value
    my $sign = Math::MPFR::Rmpfr_sgn($x);
    my $xabs = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_abs($xabs, $x, $ROUND);

    # Initialize previous state (p0, q0)
    state $p0 = Math::GMPz::Rmpz_init_nobless();
    state $q0 = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($p0, 1);
    Math::GMPz::Rmpz_set_ui($q0, 0);

    # Initialize current state (p1, q1)
    state $p1 = Math::GMPz::Rmpz_init_nobless();
    state $q1 = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($q1, 1);

    my $temp_f1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $temp_f2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    # p1 = floor(xabs)
    Math::MPFR::Rmpfr_floor($temp_f1, $xabs);
    Math::MPFR::Rmpfr_get_z($p1, $temp_f1, $ROUND);

    # remainder = xabs - p1
    my $remainder = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_sub_z($remainder, $xabs, $p1, $ROUND);

    # Working variables allocated once outside the loop
    state $m           = Math::GMPz::Rmpz_init_nobless();
    state $candidate_p = Math::GMPz::Rmpz_init_nobless();
    state $candidate_q = Math::GMPz::Rmpz_init_nobless();
    state $t           = Math::GMPz::Rmpz_init_nobless();

    while (Math::MPFR::Rmpfr_cmp_ui($remainder, 0) > 0) {

        # inv_remainder = 1 / remainder
        Math::MPFR::Rmpfr_ui_div($temp_f2, 1, $remainder, $ROUND);

        # m = floor(inv_remainder)
        Math::MPFR::Rmpfr_floor($temp_f1, $temp_f2);
        Math::MPFR::Rmpfr_get_z($m, $temp_f1, $ROUND);

        last if Math::GMPz::Rmpz_sgn($m) == 0;

        # remainder = inv_remainder - a
        Math::MPFR::Rmpfr_sub_z($remainder, $temp_f2, $m, $ROUND);

        # t = (max_den - q0) / q1   (Integer Division)
        Math::GMPz::Rmpz_sub($t, $max_den, $q0);
        Math::GMPz::Rmpz_tdiv_q($t, $t, $q1);

        if (Math::GMPz::Rmpz_cmp($m, $t) > 0) {

            if (Math::GMPz::Rmpz_cmp_ui($t, 1) >= 0) {

                # candidate_p = (t * p1) + p0
                Math::GMPz::Rmpz_mul($candidate_p, $t, $p1);
                Math::GMPz::Rmpz_add($candidate_p, $candidate_p, $p0);

                # candidate_q = (t * q1) + q0
                Math::GMPz::Rmpz_mul($candidate_q, $t, $q1);
                Math::GMPz::Rmpz_add($candidate_q, $candidate_q, $q0);

                # Calculate candidate_error = abs(candidate_p - (xabs * candidate_q)) * q1
                Math::MPFR::Rmpfr_mul_z($temp_f1, $xabs, $candidate_q, $ROUND);       # xabs * candidate_q
                Math::MPFR::Rmpfr_z_sub($temp_f1, $candidate_p, $temp_f1, $ROUND);    # candidate_p - ...
                Math::MPFR::Rmpfr_abs($temp_f1, $temp_f1, $ROUND);                    # abs(...)
                Math::MPFR::Rmpfr_mul_z($temp_f2, $temp_f1, $q1, $ROUND);

                # Calculate current_error = abs(p1 - (xabs * q1)) * candidate_q
                Math::MPFR::Rmpfr_mul_z($temp_f1, $xabs, $q1, $ROUND);                # xabs * q1
                Math::MPFR::Rmpfr_z_sub($temp_f1, $p1, $temp_f1, $ROUND);             # p1 - ...
                Math::MPFR::Rmpfr_abs($temp_f1, $temp_f1, $ROUND);                    # abs(...)
                Math::MPFR::Rmpfr_mul_z($temp_f1, $temp_f1, $candidate_q, $ROUND);

                # if candidate_error < current_error
                if (Math::MPFR::Rmpfr_cmp($temp_f2, $temp_f1) < 0) {
                    Math::GMPz::Rmpz_set($p1, $candidate_p);
                    Math::GMPz::Rmpz_set($q1, $candidate_q);
                }
            }
            last;
        }

        # (m * p1) + p0
        Math::GMPz::Rmpz_mul($t, $m, $p1);
        Math::GMPz::Rmpz_add($t, $t, $p0);
        Math::GMPz::Rmpz_set($p0, $p1);
        Math::GMPz::Rmpz_set($p1, $t);

        # (m * q1) + q0
        Math::GMPz::Rmpz_mul($t, $m, $q1);
        Math::GMPz::Rmpz_add($t, $t, $q0);
        Math::GMPz::Rmpz_set($q0, $q1);
        Math::GMPz::Rmpz_set($q1, $t);
    }

    # Restore negative sign if required
    Math::GMPz::Rmpz_neg($p1, $p1) if $sign < 0;

    my $ans_q = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_num($ans_q, $p1);
    Math::GMPq::Rmpq_set_den($ans_q, $q1);
    Math::GMPq::Rmpq_canonicalize($ans_q);

    bless \$ans_q;
}

*best_rational_approx = \&rat_approx;

sub pair {
    my ($x, $y) = @_;
    Sidef::Types::Number::Complex->new($x, $y);
}

sub __norm__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        my $r = $x * $x;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_norm($r, $x, $ROUND);
        return $r;
    }

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sqr($r, $x, $ROUND);
        return $r;
    }

  Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul($r, $x, $x);
        return $r;
    }

  Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_mul($r, $x, $x);
        return $r;
    }
}

sub norm {
    my ($x) = @_;
    bless \__norm__($$x);
}

sub conj {
    my ($x) = @_;
    ref($$x) eq 'Math::MPC' or return $x;
    my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
    Math::MPC::Rmpc_conj($r, $$x, $ROUND);
    bless \$r;
}

sub real {
    my ($x) = @_;

    if (ref($$x) eq 'Math::MPC') {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_RE($r, $$x);
        bless \$r;
    }
    else {
        $x;
    }
}

*re = \&real;

sub imag {
    my ($x) = @_;

    if (ref($$x) eq 'Math::MPC') {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_IM($r, $$x);
        bless \$r;
    }
    else {
        ZERO;
    }
}

*im        = \&imag;
*imaginary = \&imag;

sub reals {
    ($_[0]->real, $_[0]->imag);
}

sub parts {
    _array([$_[0]->reals]);
}

#
## CONSTANTS
#

sub pi {
    if (ref($_[0])) {
        goto &prime_count;
    }

    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $pi = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_const_pi($pi, $ROUND);
        bless \$pi;
    };
}

*π = \&pi;

sub tau {
    if (ref($_[0])) {
        goto &sigma0;
    }

    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $tau = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_const_pi($tau, $ROUND);
        Math::MPFR::Rmpfr_mul_2ui($tau, $tau, 1, $ROUND);
        bless \$tau;
    };
}

*τ = \&tau;

sub ln2 {
    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $ln2 = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_const_log2($ln2, $ROUND);
        bless \$ln2;
    };
}

sub EulerGamma {
    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $euler = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_const_euler($euler, $ROUND);
        bless \$euler;
    };
}

*γ           = \&EulerGamma;
*y           = \&EulerGamma;
*euler_gamma = \&EulerGamma;

sub CatalanG {
    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $catalan = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_const_catalan($catalan, $ROUND);
        bless \$catalan;
    };
}

*C = \&CatalanG;

sub i {
    my ($x) = @_;

    state %CACHE;
    my $p = CORE::int($PREC);

    my $i_obj = $CACHE{$p} //= do {
        my $i = Math::MPC::Rmpc_init2($p);
        Math::MPC::Rmpc_set_ui_ui($i, 0, 1, $ROUND);
        bless \$i;
    };

    if (ref($x)) {
        return bless \__mul__($$i_obj, $$x);
    }

    return $i_obj;
}

sub e {
    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $e = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_set_ui($e, 1, $ROUND);
        Math::MPFR::Rmpfr_exp($e, $e, $ROUND);
        bless \$e;
    };
}

sub phi {
    if (ref($_[0])) {
        goto &euler_phi;
    }

    state %CACHE;
    my $p = CORE::int($PREC);

    return $CACHE{$p} //= do {
        my $phi = Math::MPFR::Rmpfr_init2($p);
        Math::MPFR::Rmpfr_set_ui($phi, 5, $ROUND);
        Math::MPFR::Rmpfr_div_2ui($phi, $phi, 2, $ROUND);    # phi=5/4
        Math::MPFR::Rmpfr_sqrt($phi, $phi, $ROUND);
        Math::MPFR::Rmpfr_add_d($phi, $phi, 0.5, $ROUND);
        bless \$phi;
    };
}

*φ = \&phi;

sub _nan {
    state $nan = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_nan($r);
        $r;    # Unblessed for internal use
    };
}

sub nan {
    state $nan = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_nan($r);
        bless \$r;
    };
}

*NaN = \&nan;

sub _inf {
    state $inf = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_inf($r, 1);
        $r;    # Unblessed for internal use
    };
}

sub inf {
    state $inf = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_inf($r, 1);
        bless \$r;
    };
}

*Inf = \&inf;

sub _ninf {
    state $ninf = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_inf($r, -1);
        $r;    # Unblessed for internal use
    };
}

sub ninf {
    state $ninf = do {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_inf($r, -1);
        bless \$r;
    };
}

*zero = \&ZERO;
*one  = \&ONE;
*mone = \&MONE;

#
## Arithmetic operations
#

sub __add__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        my $r = $x + $y;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $r =
          ($x < 0)
          ? Math::GMPz::Rmpz_init_set_si($x)
          : Math::GMPz::Rmpz_init_set_ui($x);
        ($y < 0)
          ? Math::GMPz::Rmpz_sub_ui($r, $r, -$y)
          : Math::GMPz::Rmpz_add_ui($r, $r, $y);
        return $r;
    }

  Math_GMPz__Scalar: {
        $y || return Math::GMPz::Rmpz_init_set($x);
        my $r = Math::GMPz::Rmpz_init();
        ($y < 0)
          ? Math::GMPz::Rmpz_sub_ui($r, $x, -$y)
          : Math::GMPz::Rmpz_add_ui($r, $x, $y);
        ## $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPq__Scalar: {
        my $r = Math::GMPq::Rmpq_init();

        if ($y == 0) {
            Math::GMPq::Rmpq_set($r, $x);
            return $r;
        }

        ($y < 0)
          ? Math::GMPq::Rmpq_set_si($r, $y, 1)
          : Math::GMPq::Rmpq_set_ui($r, $y, 1);
        Math::GMPq::Rmpq_add($r, $r, $x);
        return $r;
    }

  Math_MPFR__Scalar: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);

        if ($y == 0) {
            Math::MPFR::Rmpfr_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPFR::Rmpfr_sub_ui($r, $x, -$y, $ROUND)
          : Math::MPFR::Rmpfr_add_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Scalar: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        if ($y == 0) {
            Math::MPC::Rmpc_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPC::Rmpc_sub_ui($r, $x, -$y, $ROUND)
          : Math::MPC::Rmpc_add_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Scalar__Math_GMPz: {
        ($x, $y) = ($y, $x);
        goto Math_GMPz__Scalar;
    }

  Scalar__Math_GMPq: {
        ($x, $y) = ($y, $x);
        goto Math_GMPq__Scalar;
    }

  Scalar__Math_MPFR: {
        ($x, $y) = ($y, $x);
        goto Math_MPFR__Scalar;
    }

  Scalar__Math_MPC: {
        ($x, $y) = ($y, $x);
        goto Math_MPC__Scalar;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_add($r, $x, $y);
        ## $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPz__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_add_z($r, $y, $x);
        return $r;
    }

  Math_GMPz__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add_z($r, $y, $x, $ROUND);
        return $r;
    }

  Math_GMPz__Math_MPC: {
        my $c = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($c, $x, $ROUND);
        Math::MPC::Rmpc_add($c, $c, $y, $ROUND);
        return $c;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_add($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_GMPz: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_add_z($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add_q($r, $y, $x, $ROUND);
        return $r;
    }

  Math_GMPq__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $x, $ROUND);
        Math::MPC::Rmpc_add($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPq: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add_q($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPz: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_add_fr($r, $y, $x, $ROUND);
        return $r;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_add($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_add_fr($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPz: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $y, $ROUND);
        Math::MPC::Rmpc_add($r, $r, $x, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPq: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $y, $ROUND);
        Math::MPC::Rmpc_add($r, $r, $x, $ROUND);
        return $r;
    }
}

sub add {
    my ($x, $y) = @_;

    my $ref = ref($y);

    if (exists $NUMBER_CLASSES{$ref}) {
        return $y->add($x);
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__add__($$x, $$y);
}

sub __sub__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

  Scalar__Scalar: {
        my $r = $x - $y;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $r =
          ($x < 0)
          ? Math::GMPz::Rmpz_init_set_si($x)
          : Math::GMPz::Rmpz_init_set_ui($x);
        ($y < 0)
          ? Math::GMPz::Rmpz_add_ui($r, $r, -$y)
          : Math::GMPz::Rmpz_sub_ui($r, $r, $y);
        return $r;
    }

  Math_GMPz__Scalar: {
        $y || return Math::GMPz::Rmpz_init_set($x);
        state $r = Math::GMPz::Rmpz_init_nobless();
        ($y < 0)
          ? Math::GMPz::Rmpz_add_ui($r, $x, -$y)
          : Math::GMPz::Rmpz_sub_ui($r, $x, $y);
        my $r2 =
            Math::GMPz::Rmpz_fits_slong_p($r)
          ? Math::GMPz::Rmpz_get_si($r)
          : Math::GMPz::Rmpz_init_set($r);
        return $r2;
    }

  Math_GMPq__Scalar: {
        my $r = Math::GMPq::Rmpq_init();

        if ($y == 0) {
            Math::GMPq::Rmpq_set($r, $x);
            return $r;
        }

        ($y < 0)
          ? Math::GMPq::Rmpq_set_si($r, $y, 1)
          : Math::GMPq::Rmpq_set_ui($r, $y, 1);
        Math::GMPq::Rmpq_sub($r, $x, $r);
        return $r;
    }

  Math_MPFR__Scalar: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);

        if ($y == 0) {
            Math::MPFR::Rmpfr_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPFR::Rmpfr_add_ui($r, $x, -$y, $ROUND)
          : Math::MPFR::Rmpfr_sub_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Scalar: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        if ($y == 0) {
            Math::MPC::Rmpc_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPC::Rmpc_add_ui($r, $x, -$y, $ROUND)
          : Math::MPC::Rmpc_sub_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Scalar__Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();

        if ($x == 0) {
            Math::GMPz::Rmpz_neg($r, $y);
            return $r;
        }

        ($x < 0)
          ? do {
            Math::GMPz::Rmpz_add_ui($r, $y, -$x);
            Math::GMPz::Rmpz_neg($r, $r);
          }
          : Math::GMPz::Rmpz_ui_sub($r, $x, $y);
        return $r;
    }

  Scalar__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();

        if ($x == 0) {
            Math::GMPq::Rmpq_neg($r, $y);
            return $r;
        }

        ($x < 0)
          ? Math::GMPq::Rmpq_set_si($r, $x, 1)
          : Math::GMPq::Rmpq_set_ui($r, $x, 1);
        Math::GMPq::Rmpq_sub($r, $r, $y);
        return $r;
    }

  Scalar__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);
        ($x < 0)
          ? Math::MPFR::Rmpfr_si_sub($r, $x, $y, $ROUND)
          : Math::MPFR::Rmpfr_ui_sub($r, $x, $y, $ROUND);
        return $r;
    }

  Scalar__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        if ($x == 0) {
            Math::MPC::Rmpc_neg($r, $y, $ROUND);
            return $r;
        }

        Math::MPC::Rmpc_set_ui($r, (($x < 0) ? (-$x) : $x), $ROUND);
        Math::MPC::Rmpc_neg($r, $r, $ROUND) if ($x < 0);
        Math::MPC::Rmpc_sub($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_sub($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_GMPz: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_sub_z($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sub_q($r, $y, $x, $ROUND);
        Math::MPFR::Rmpfr_neg($r, $r, $ROUND);
        return $r;
    }

  Math_GMPq__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $x, $ROUND);
        Math::MPC::Rmpc_sub($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_sub($r, $x, $y);
        $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPz__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_z_sub($r, $x, $y);
        return $r;
    }

  Math_GMPz__Math_MPFR: {

#<<<
        state $has_z_sub = (Math::MPFR::MPFR_VERSION_MAJOR() >  3)
                        || (Math::MPFR::MPFR_VERSION_MAJOR() == 3
                        &&  Math::MPFR::MPFR_VERSION_MINOR() >= 1);
#>>>

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        $has_z_sub ? Math::MPFR::Rmpfr_z_sub($r, $x, $y, $ROUND) : do {
            Math::MPFR::Rmpfr_sub_z($r, $y, $x, $ROUND);
            Math::MPFR::Rmpfr_neg($r, $r, $ROUND);
        };

        return $r;
    }

  Math_GMPz__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $x, $ROUND);
        Math::MPC::Rmpc_sub($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sub($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPq: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sub_q($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPz: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sub_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr($r, $x, $ROUND);
        Math::MPC::Rmpc_sub($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sub($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr($r, $y, $ROUND);
        Math::MPC::Rmpc_sub($r, $x, $r, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPz: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $y, $ROUND);
        Math::MPC::Rmpc_sub($r, $x, $r, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPq: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $y, $ROUND);
        Math::MPC::Rmpc_sub($r, $x, $r, $ROUND);
        return $r;
    }
}

sub sub {
    my ($x, $y) = @_;

    my $ref = ref($y);

    if (exists $NUMBER_CLASSES{$ref}) {
        return $y->with_value($x)->sub($y);
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__sub__($$x, $$y);
}

sub __mul__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        my $r = $x * $y;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $r =
          ($x < 0)
          ? Math::GMPz::Rmpz_init_set_si($x)
          : Math::GMPz::Rmpz_init_set_ui($x);
        ($y < 0)
          ? Math::GMPz::Rmpz_mul_si($r, $r, $y)
          : Math::GMPz::Rmpz_mul_ui($r, $r, $y);
        return $r;
    }

  Math_GMPz__Scalar: {
        $y || return 0;
        $y == 1 and return Math::GMPz::Rmpz_init_set($x);
        my $r = Math::GMPz::Rmpz_init();
        ($y < 0)
          ? Math::GMPz::Rmpz_mul_si($r, $x, $y)
          : Math::GMPz::Rmpz_mul_ui($r, $x, $y);
        ## $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPq__Scalar: {
        $y || return 0;

        my $r = Math::GMPq::Rmpq_init();

        if ($y == 1) {
            Math::GMPq::Rmpq_set($r, $x);
            return $r;
        }

        ($y < 0)
          ? Math::GMPq::Rmpq_set_si($r, $y, 1)
          : Math::GMPq::Rmpq_set_ui($r, $y, 1);
        Math::GMPq::Rmpq_mul($r, $r, $x);
        return $r;
    }

  Math_MPFR__Scalar: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);

        if ($y == 1) {
            Math::MPFR::Rmpfr_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPFR::Rmpfr_mul_si($r, $x, $y, $ROUND)
          : Math::MPFR::Rmpfr_mul_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Scalar: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        if ($y == 1) {
            Math::MPC::Rmpc_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPC::Rmpc_mul_si($r, $x, $y, $ROUND)
          : Math::MPC::Rmpc_mul_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Scalar__Math_GMPz: {
        ($x, $y) = ($y, $x);
        goto Math_GMPz__Scalar;
    }

  Scalar__Math_GMPq: {
        ($x, $y) = ($y, $x);
        goto Math_GMPq__Scalar;
    }

  Scalar__Math_MPFR: {
        ($x, $y) = ($y, $x);
        goto Math_MPFR__Scalar;
    }

  Scalar__Math_MPC: {
        ($x, $y) = ($y, $x);
        goto Math_MPC__Scalar;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_mul($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_GMPz: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_mul_z($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_mul_q($r, $y, $x, $ROUND);
        return $r;
    }

  Math_GMPq__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $x, $ROUND);
        Math::MPC::Rmpc_mul($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul($r, $x, $y);
        ## $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPz__Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_mul_z($r, $y, $x);
        return $r;
    }

  Math_GMPz__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_mul_z($r, $y, $x, $ROUND);
        return $r;
    }

  Math_GMPz__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $x, $ROUND);
        Math::MPC::Rmpc_mul($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_mul($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPq: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_mul_q($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPz: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_mul_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_mul_fr($r, $y, $x, $ROUND);
        return $r;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_mul($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_mul_fr($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPz: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $y, $ROUND);
        Math::MPC::Rmpc_mul($r, $r, $x, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPq: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $y, $ROUND);
        Math::MPC::Rmpc_mul($r, $r, $x, $ROUND);
        return $r;
    }
}

sub mul {
    my ($x, $y) = @_;

    my $ref = ref($y);
    if (exists $NUMBER_CLASSES{$ref}) {
        return $y->mul($x);
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__mul__($$x, $$y);
}

sub __div__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        if ($y != 0 and $x % $y == 0) {
            my $r =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divint($x, $y)
              : Math::Prime::Util::GMP::divint($x, $y);
            if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
                return $r;
            }
        }
        $x = _any2mpz($x);
        goto Math_GMPz__Scalar;
    }

  Math_GMPz__Scalar: {

        $y == 1 and return Math::GMPz::Rmpz_init_set($x);

        # Check for division by zero
        $y || do {
            $y = _any2mpz($y);
            goto Math_GMPz__Math_GMPz;
        };

        # Check for exact divisibility
        if (Math::GMPz::Rmpz_divisible_ui_p($x, CORE::abs($y))) {
            state $r = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_divexact_ui($r, $x, CORE::abs($y));
            Math::GMPz::Rmpz_neg($r, $r) if ($y < 0);
            my $r2 =
                Math::GMPz::Rmpz_fits_slong_p($r)
              ? Math::GMPz::Rmpz_get_si($r)
              : Math::GMPz::Rmpz_init_set($r);
            return $r2;
        }

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_ui($r, 1, CORE::abs($y));
        Math::GMPq::Rmpq_set_num($r, $x);
        Math::GMPq::Rmpq_neg($r, $r) if ($y < 0);
        Math::GMPq::Rmpq_canonicalize($r);
        return $r;
    }

  Scalar__Math_GMPz: {

        # Check for division by zero
        Math::GMPz::Rmpz_sgn($y) || do {
            $y = _mpz2mpfr($y);
            goto Scalar__Math_MPFR;
        };

        my $r = Math::GMPq::Rmpq_init();

        if ($x == 1 or $x == -1) {
            Math::GMPq::Rmpq_set_z($r, $y);
            Math::GMPq::Rmpq_inv($r, $r);
            Math::GMPq::Rmpq_neg($r, $r) if ($x < 0);
            return $r;
        }

        ($x < 0)
          ? Math::GMPq::Rmpq_set_si($r, $x, 1)
          : Math::GMPq::Rmpq_set_ui($r, $x, 1);

        Math::GMPq::Rmpq_set_den($r, $y);
        Math::GMPq::Rmpq_canonicalize($r);

        # If the result is an integer, return a GMPz object
        if (Math::GMPq::Rmpq_integer_p($r)) {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPq::Rmpq_get_num($z, $r);
            return $z;
        }

        return $r;
    }

  Math_GMPq__Scalar: {

        # Check for division by zero
        $y || do {
            $y = _any2mpz($y);
            goto Math_GMPq__Math_GMPz;
        };

        my $r = Math::GMPq::Rmpq_init();

        if ($y == 1) {
            Math::GMPq::Rmpq_set($r, $x);
            return $r;
        }

        ($y < 0)
          ? Math::GMPq::Rmpq_set_si($r, -1, -$y)
          : Math::GMPq::Rmpq_set_ui($r, 1, $y);
        Math::GMPq::Rmpq_mul($r, $r, $x);
        return $r;
    }

  Scalar__Math_GMPq: {

        # Check for division by zero
        Math::GMPq::Rmpq_sgn($y) || do {
            $y = _mpq2mpfr($y);
            goto Scalar__Math_MPFR;
        };

        my $r = Math::GMPq::Rmpq_init();

        if ($x == 1 or $x == -1) {
            Math::GMPq::Rmpq_inv($r, $y);
            Math::GMPq::Rmpq_neg($r, $r) if ($x < 0);
            return $r;
        }

        ($x < 0)
          ? Math::GMPq::Rmpq_set_si($r, $x, 1)
          : Math::GMPq::Rmpq_set_ui($r, $x, 1);

        Math::GMPq::Rmpq_div($r, $r, $y);

        return $r;
    }

  Math_MPFR__Scalar: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);

        if ($y == 1) {
            Math::MPFR::Rmpfr_set($r, $x, $ROUND);
            return $r;
        }

        ($y < 0)
          ? Math::MPFR::Rmpfr_div_si($r, $x, $y, $ROUND)
          : Math::MPFR::Rmpfr_div_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Scalar__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2($PREC);
        ($x < 0)
          ? Math::MPFR::Rmpfr_si_div($r, $x, $y, $ROUND)
          : Math::MPFR::Rmpfr_ui_div($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Scalar: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        if ($y == 1) {
            Math::MPC::Rmpc_set($r, $x, $ROUND);
            return $r;
        }

        Math::MPC::Rmpc_set_ui($r, (($y < 0) ? (-$y) : $y), $ROUND);
        Math::MPC::Rmpc_neg($r, $r, $ROUND) if ($y < 0);
        Math::MPC::Rmpc_div($r, $x, $r, $ROUND);
        return $r;
    }

  Scalar__Math_MPC: {

        # XXX: don't use Rmpc_ui_div(), as it is broken in mpc=1.4.0
        my $r = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_set_ui($r, (($x < 0) ? (-$x) : $x), $ROUND);
        Math::MPC::Rmpc_neg($r, $r, $ROUND) if ($x < 0);
        Math::MPC::Rmpc_div($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {

        # Check for division by zero
        Math::GMPq::Rmpq_sgn($y) || do {
            $x = _mpq2mpfr($x);
            goto Math_MPFR__Math_GMPq;
        };

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_div($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_GMPz: {

        # Check for division by zero
        Math::GMPz::Rmpz_sgn($y) || do {
            $x = _mpq2mpfr($x);
            goto Math_MPFR__Math_GMPz;
        };

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_div_z($r, $x, $y);
        return $r;
    }

  Math_GMPq__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_q_div($r, $x, $y, $ROUND);
        return $r;
    }

  Math_GMPq__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $x, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {

        # Check for division by zero
        Math::GMPz::Rmpz_sgn($y) || do {
            $x = _mpz2mpfr($x);
            goto Math_MPFR__Math_GMPz;
        };

        # Check for exact divisibility
        if (Math::GMPz::Rmpz_divisible_p($x, $y)) {
            state $r = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_divexact($r, $x, $y);
            return (
                      Math::GMPz::Rmpz_fits_slong_p($r)
                    ? Math::GMPz::Rmpz_get_si($r)
                    : Math::GMPz::Rmpz_init_set($r)
                   );
        }

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_num($r, $x);
        Math::GMPq::Rmpq_set_den($r, $y);
        Math::GMPq::Rmpq_canonicalize($r);
        return $r;
    }

  Math_GMPz__Math_GMPq: {

        # Check for division by zero
        Math::GMPq::Rmpq_sgn($y) || do {
            $x = _mpz2mpfr($x);
            goto Math_MPFR__Math_GMPq;
        };

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_z_div($r, $x, $y);
        return $r;
    }

  Math_GMPz__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_z_div($r, $x, $y, $ROUND);
        return $r;
    }

  Math_GMPz__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $x, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPq: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div_q($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPz: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {

        # XXX: don't use Rmpc_fr_div(), as it is broken in mpc=1.4.0
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr($r, $x, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $y, $ROUND);
        return $r;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_div($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_div_fr($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPz: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_z($r, $y, $ROUND);
        Math::MPC::Rmpc_div($r, $x, $r, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPq: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_q($r, $y, $ROUND);
        Math::MPC::Rmpc_div($r, $x, $r, $ROUND);
        return $r;
    }
}

sub div {
    my ($x, $y) = @_;

    my $ref = ref($y);
    if (exists $NUMBER_CLASSES{$ref}) {
        return $y->with_value($x)->div($y);
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__div__($$x, $$y);
}

# Modular operations

sub addmod {
    my ($x, $y, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $m = $$m;

    if (FAST_MODE and !ref($m) and $m > 0 and !ref($x) and !ref($y)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::addmod($x, $y, $m)
          : Math::Prime::Util::GMP::addmod($x, $y, $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add($r, $x, $y);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub addmulmod {
    my ($x, $y, $z, $m) = @_;

    # r = (x + y*z) % m

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and !ref($m) and $m > 0 and !ref($x) and !ref($y) and !ref($z)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::addmod($x, Math::Prime::Util::mulmod($y, $z, $m), $m)
          : Math::Prime::Util::GMP::addmod($x, Math::Prime::Util::GMP::mulmod($y, $z, $m), $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init_set($x);
    Math::GMPz::Rmpz_addmul($r, $y, $z);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub submulmod {
    my ($x, $y, $z, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($x) and !ref($y) and !ref($z)) {
        my $r = Math::Prime::Util::submod($x, Math::Prime::Util::mulmod($y, $z, $m), $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init_set($x);
    Math::GMPz::Rmpz_submul($r, $y, $z);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub submod {
    my ($x, $y, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($x) and !ref($y)) {
        my $r = Math::Prime::Util::submod($x, $y, $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub($r, $x, $y);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub mulmod {
    my ($x, $y, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $m = $$m;

    if (FAST_MODE and !ref($m) and $m > 0 and !ref($x) and !ref($y)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::mulmod($x, $y, $m)
          : Math::Prime::Util::GMP::mulmod($x, $y, $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $x, $y);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub muladdmod {
    my ($x, $y, $z, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($x) and !ref($y) and !ref($z)) {
        my $r = Math::Prime::Util::muladdmod($x, $y, $z, $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init_set($z);
    Math::GMPz::Rmpz_addmul($r, $x, $y);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub mulsubmod {
    my ($x, $y, $z, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($x) and !ref($y) and !ref($z)) {
        my $r = Math::Prime::Util::mulsubmod($x, $y, $z, $m);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init_set($z);
    Math::GMPz::Rmpz_neg($r, $r);
    Math::GMPz::Rmpz_addmul($r, $x, $y);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub muladdmulmod {
    my ($w, $x, $y, $z, $m) = @_;

    ref($x) eq __PACKAGE__ or _valid(\$x);
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $w = $$w;
    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($w) and !ref($x) and !ref($y) and !ref($z)) {
        my $r = Math::Prime::Util::muladdmod($w, $x, Math::Prime::Util::mulmod($y, $z, $m), $m);
        return bless \$r;
    }

    $w = _any2mpz($w) // goto &nan;
    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $w, $x);
    Math::GMPz::Rmpz_addmul($r, $y, $z);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

sub mulsubmulmod {
    my ($w, $x, $y, $z, $m) = @_;

    ref($x) eq __PACKAGE__ or _valid(\$x);
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $w = $$w;
    $x = $$x;
    $y = $$y;
    $z = $$z;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($m) and $m > 0 and !ref($w) and !ref($x) and !ref($y) and !ref($z)) {
        my $r = Math::Prime::Util::mulsubmod($w, $x, Math::Prime::Util::mulmod($y, $z, $m), $m);
        return bless \$r;
    }

    $w = _any2mpz($w) // goto &nan;
    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;
    $z = _any2mpz($z) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) == 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $w, $x);
    Math::GMPz::Rmpz_submul($r, $y, $z);
    Math::GMPz::Rmpz_mod($r, $r, $m);
    bless \$r;
}

#
## Integer operations
#

sub iadd {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        return bless \__add__($x, $y);
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add($r, $x, $y);
    bless \$r;
}

*addint = \&iadd;

sub isub {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        return bless \__sub__($x, $y);
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub($r, $x, $y);
    bless \$r;
}

*subint = \&isub;

sub imul {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        return bless \__mul__($x, $y);
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $x, $y);
    bless \$r;
}

*mulint = \&imul;

sub imod {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        return bless \__mod__($x, $y);
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    my $sign_y = Math::GMPz::Rmpz_sgn($y)
      || goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($r, $x, $y);

    if (!Math::GMPz::Rmpz_sgn($r)) {
        ## OK
    }
    elsif ($sign_y < 0) {
        Math::GMPz::Rmpz_add($r, $r, $y);
    }

    bless \$r;
}

*modint = \&imod;

sub idiv {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y) and $x >= 0 and $y > 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($x, $y)
          : Math::Prime::Util::GMP::divint($x, $y);
        return bless \$r;
    }

    $x = _any2mpz($x) // (goto &nan);

    if (FAST_MODE and !ref($y) and $y > 0) {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_div_ui($r, $x, $y);
        my $r2 =
            Math::GMPz::Rmpz_fits_slong_p($r)
          ? Math::GMPz::Rmpz_get_si($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    $y = _any2mpz($y) // (goto &nan);

    # Detect division by zero
    Math::GMPz::Rmpz_sgn($y) || do {
        my $sign = Math::GMPz::Rmpz_sgn($x);

        if ($sign == 0) {    # 0/0
            goto &nan;
        }
        elsif ($sign > 0) {    # x/0 where: x > 0
            goto &inf;
        }
        else {                 # x/0 where: x < 0
            goto &ninf;
        }
    };

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_div($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*fdiv       = \&idiv;
*fld        = \&idiv;
*idiv_floor = \&idiv;
*divint     = \&idiv;

sub idiv_ceil {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y) and $x >= 0 and $y > 0) {
        my ($q, $r) =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divrem($x, $y)
          : Math::Prime::Util::GMP::divrem($x, $y);
        ++$q if $r;
        return bless \$q;
    }

    $x = _any2mpz($x) // (goto &nan);

    if (FAST_MODE and !ref($y) and $y > 0) {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_cdiv_q_ui($r, $x, $y);
        my $r2 =
            Math::GMPz::Rmpz_fits_slong_p($r)
          ? Math::GMPz::Rmpz_get_si($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    $y = _any2mpz($y) // (goto &nan);

    # Detect division by zero
    Math::GMPz::Rmpz_sgn($y) || do {
        my $sign = Math::GMPz::Rmpz_sgn($x);

        if ($sign == 0) {    # 0/0
            goto &nan;
        }
        elsif ($sign > 0) {    # x/0 where: x > 0
            goto &inf;
        }
        else {                 # x/0 where: x < 0
            goto &ninf;
        }
    };

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_cdiv_q($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*cld = \&idiv_ceil;

sub idiv_trunc {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // (goto &nan);
    $y = _any2mpz($$y) // (goto &nan);

    # Detect division by zero
    Math::GMPz::Rmpz_sgn($y) || do {
        my $sign = Math::GMPz::Rmpz_sgn($x);

        if ($sign == 0) {    # 0/0
            goto &nan;
        }
        elsif ($sign > 0) {    # x/0 where: x > 0
            goto &inf;
        }
        else {                 # x/0 where: x < 0
            goto &ninf;
        }
    };

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_tdiv_q($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*trd = \&idiv_trunc;

sub idiv_round {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // (goto &nan);
    $y = _any2mpz($$y) // (goto &nan);

    # Detect division by zero
    Math::GMPz::Rmpz_sgn($y) || do {
        my $sign = Math::GMPz::Rmpz_sgn($x);

        if ($sign == 0) {    # 0/0
            goto &nan;
        }
        elsif ($sign > 0) {    # x/0 where: x > 0
            goto &inf;
        }
        else {                 # x/0 where: x < 0
            goto &ninf;
        }
    };

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set($r, $y);
    Math::GMPz::Rmpz_addmul_ui($r, $x, 2);
    Math::GMPz::Rmpz_div($r, $r, $y);
    Math::GMPz::Rmpz_div_2exp($r, $r, 1);
    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*rdd = \&idiv_round;

sub __neg__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        my $r = -$x;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $r =
          ($x < 0)
          ? Math::GMPz::Rmpz_init_set_si($x)
          : Math::GMPz::Rmpz_init_set_ui($x);
        Math::GMPz::Rmpz_neg($r, $r);
        return $r;
    }

  Math_GMPz: {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_neg($r, $x);
        return $r;
    }

  Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_neg($r, $x);
        return $r;
    }

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_neg($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_neg($r, $x, $ROUND);
        return $r;
    }
}

sub neg {
    my ($x) = @_;
    bless \__neg__($$x);
}

sub ineg {
    my ($x) = @_;
    $x->to_i->neg;
}

*negint = \&ineg;

sub __abs__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return CORE::abs($x)});

  Math_GMPz: {
        Math::GMPz::Rmpz_sgn($x) >= 0 and return $x;
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_abs($r, $x);
        return $r;
    }

  Math_GMPq: {
        Math::GMPq::Rmpq_sgn($x) >= 0 and return $x;
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_abs($r, $x);
        return $r;
    }

  Math_MPFR: {
        Math::MPFR::Rmpfr_sgn($x) >= 0 and return $x;
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_abs($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($r, $x, $ROUND);
        return $r;
    }
}

sub abs {
    my ($x) = @_;
    bless \__abs__($$x);
}

sub iabs {
    my ($x) = @_;
    $x->to_i->abs;
}

*absint = \&iabs;

sub __inv__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        CORE::abs($x) == 1 and return $x;
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_GMPq: {

        # Check for division by zero
        Math::GMPq::Rmpq_sgn($x) || do {
            $x = _mpq2mpfr($x);
            goto Math_MPFR;
        };

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_inv($r, $x);
        return $r;
    }

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        return $r;
    }

  Math_GMPz: {

        # Check for division by zero
        Math::GMPz::Rmpz_sgn($x) || do {
            $x = _mpz2mpfr($x);
            goto Math_MPFR;
        };

        Math::GMPz::Rmpz_cmpabs_ui($x, 1) == 0
          and return $x;

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_z($r, $x);
        Math::GMPq::Rmpq_inv($r, $r);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        return $r;
    }
}

sub inv {
    my ($x) = @_;
    bless \__inv__($$x);
}

sub sqr {
    my ($x) = @_;
    bless \__mul__($$x, $$x);
}

*square = \&sqr;

sub cube {
    my ($x) = @_;
    bless \__pow__($$x, 3);
}

sub __sqrt__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Complex for x < 0
        if (Math::MPFR::Rmpfr_sgn($x) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_sqrt($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sqrt($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sqrt($r, $x, $ROUND);
        return $r;
    }
}

sub sqrt {
    my ($x) = @_;
    bless \__sqrt__(_any2mpfr_mpc($$x));
}

sub sqrtQ {
    my ($x) = @_;
    Sidef::Types::Number::Quadratic->new(ZERO, ONE, $x);
}

sub __cbrt__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Complex for x < 0
        if (Math::MPFR::Rmpfr_sgn($x) < 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_cbrt($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $three_inv = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_ui($three_inv, 3, $ROUND);
        Math::MPFR::Rmpfr_ui_div($three_inv, 1, $three_inv, $ROUND);

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_pow_fr($r, $x, $three_inv, $ROUND);
        return $r;
    }
}

sub cbrt {
    my ($x) = @_;
    bless \__cbrt__(_any2mpfr_mpc($$x));
}

sub __iroot__ {
    my ($x, $y) = @_;

    # $x is a Math::GMPz object
    # $y is a signed integer

    if ($y == 0) {
        Math::GMPz::Rmpz_sgn($x) || return $x;    # 0^Inf = 0

        # 1^Inf = 1 ; (-1)^Inf = 1
        if (Math::GMPz::Rmpz_cmpabs_ui($x, 1) == 0) {
            return 1;
        }

        goto &_inf;
    }
    elsif ($y < 0) {
        my $sign = Math::GMPz::Rmpz_sgn($x) || goto &_inf;    # 1 / 0^k = Inf
        Math::GMPz::Rmpz_cmp_ui($x, 1)      || return $x;     # 1 / 1^k = 1

        if ($sign < 0) {
            goto &_nan;
        }

        return 0;
    }
    elsif ($y % 2 == 0 and Math::GMPz::Rmpz_sgn($x) < 0) {
        goto &_nan;
    }

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_root($r, $x, $y);
    Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
}

sub iroot {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if (FAST_MODE and $y >= 1 and !ref($x) and $x >= 0) {
        if ($y == 1) {
            return bless \$x;
        }
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::rootint($x, $y)
          : Math::Prime::Util::GMP::rootint($x, $y);
        return bless \$r;
    }

    bless \__iroot__(_any2mpz($x) // (goto &nan), $y);
}

*rootint = \&iroot;

sub iroot_ceil {
    my ($n, $k) = @_;

    if ($n->is_power($k)) {
        return $n->iroot($k);
    }

    return $n->iroot($k)->inc;
}

sub isqrt {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x) and $x >= 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::sqrtint($x)
          : Math::Prime::Util::GMP::sqrtint($x);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    Math::GMPz::Rmpz_sgn($x) < 0 and goto &nan;

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sqrt($r, $x);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*sqrtint = \&isqrt;

sub icbrt {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x) and $x >= 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::rootint($x, 3)
          : Math::Prime::Util::GMP::rootint($x, 3);
        return bless \$r;
    }

    bless \__iroot__(_any2mpz($x) // (goto &nan), 3);
}

*cbrtint = \&icbrt;

sub isqrtrem {
    my ($x) = @_;

    $x = _any2mpz($$x) // goto &nan;

    Math::GMPz::Rmpz_sgn($x) < 0
      and return ((nan()) x 2);

    my $r = Math::GMPz::Rmpz_init();
    my $s = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sqrtrem($r, $s, $x);
    ((bless \$r), (bless \$s));
}

sub irootrem {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // (goto &nan);
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if ($y == 0) {
        Math::GMPz::Rmpz_sgn($x) || return (ZERO, MONE);    # 0^Inf = 0

        if (Math::GMPz::Rmpz_cmpabs_ui($x, 1) == 0) {       # 1^Inf = 1 ; (-1)^Inf = 1
            return (ONE, bless \__dec__($x));
        }

        return (inf(), bless \__dec__($x));
    }
    elsif ($y < 0) {
        my $sign = Math::GMPz::Rmpz_sgn($x) || return (inf(), ZERO);    # 1 / 0^k = Inf
        Math::GMPz::Rmpz_cmp_ui($x, 1) == 0 and return (ONE, ZERO);     # 1 / 1^k = 1
        return ($sign < 0 ? (nan(), nan()) : (ZERO, ninf()));
    }
    elsif ($y % 2 == 0 and Math::GMPz::Rmpz_sgn($x) < 0) {
        return (nan(), nan());
    }

    my $r = Math::GMPz::Rmpz_init();
    my $s = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_rootrem($r, $s, $x, $y);
    ((bless \$r), (bless \$s));
}

sub __pow__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {

        $y || return 1;

        if ($x >= 0 and $y > 0) {
            $x || return 0;
            if (CORE::log($x) * $y < CORE::log(ULONG_MAX)) {
                $x == 1 and return 1;
                my $r =
                  HAS_PRIME_UTIL
                  ? Math::Prime::Util::powint($x, $y)
                  : Math::Prime::Util::GMP::powint($x, $y);
                if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
                    return $r;
                }
            }
            my $r = Math::GMPz::Rmpz_init();
            $y < ULONG_MAX or goto &_nan;
            Math::GMPz::Rmpz_ui_pow_ui($r, $x, $y);
            return $r;
        }
        elsif ($x == -1) {
            return (($y % 2 == 0) ? 1 : -1);
        }
        elsif ($x < 0 and $y > 0) {
            if (CORE::log(CORE::abs($x)) * $y < CORE::log(CORE::abs(LONG_MIN))) {
                my $r =
                  HAS_PRIME_UTIL
                  ? Math::Prime::Util::powint($x, $y)
                  : Math::Prime::Util::GMP::powint($x, $y);
                if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
                    return $r;
                }
            }
            my $r = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_ui_pow_ui($r, CORE::abs($x), $y);
            Math::GMPz::Rmpz_neg($r, $r) if ($y & 1);
            return $r;
        }

        $x = _any2mpz($x);
        goto Math_GMPz__Scalar;
    }

  Scalar__Math_GMPz: {
        $y = Math::GMPz::Rmpz_get_d($y);
        goto Scalar__Scalar;
    }

  Scalar__Math_GMPq: {
        $x = _any2mpz($x);
        goto Math_GMPz__Math_GMPq;
    }

  Scalar__Math_MPC: {
        $x = _any2mpz($x);
        goto Math_GMPz__Math_MPC;
    }

  Scalar__Math_MPFR: {

        if ($x >= 0) {
            my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_ui_pow($r, $x, $y, $ROUND);
            return $r;
        }

        $x = _any2mpfr($x);
        goto Math_MPFR__Math_MPFR;
    }

    #
    ## GMPq
    #
  Math_GMPq__Scalar: {

        $y || return 1;

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_pow_ui($r, $x, CORE::abs($y));

        if ($y < 0) {
            Math::GMPq::Rmpq_sgn($r) || goto &_inf;
            Math::GMPq::Rmpq_inv($r, $r);
        }

        return $r;
    }

  Math_GMPq__Math_GMPq: {

        # Integer power
        if (Math::GMPq::Rmpq_integer_p($y)) {
            $y = Math::GMPq::Rmpq_get_d($y);
            goto Math_GMPq__Scalar;
        }

        # (-x)^(a/b) is a complex number
        elsif (Math::GMPq::Rmpq_sgn($x) < 0) {
            ($x, $y) = (_mpq2mpc($x), _mpq2mpc($y));
            goto Math_MPC__Math_MPC;
        }

        ($x, $y) = (_mpq2mpfr($x), _mpq2mpfr($y));
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPq__Math_GMPz: {
        $y = Math::GMPz::Rmpz_get_d($y);
        goto Math_GMPq__Scalar;
    }

  Math_GMPq__Math_MPFR: {
        $x = _mpq2mpfr($x);
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPq__Math_MPC: {
        $x = _mpq2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## GMPz
    #
  Math_GMPz__Scalar: {

        $y || return 1;

        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($r, $x, CORE::abs($y));

        if ($y < 0) {
            Math::GMPz::Rmpz_sgn($r) || goto &_inf;

            my $q = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_set_z($q, $r);
            Math::GMPq::Rmpq_inv($q, $q);
            return $q;
        }

        ## $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPz__Math_GMPz: {
        $y = Math::GMPz::Rmpz_get_d($y);
        goto Math_GMPz__Scalar;
    }

  Math_GMPz__Math_GMPq: {
        if (Math::GMPq::Rmpq_integer_p($y)) {
            $y = Math::GMPq::Rmpq_get_d($y);
            goto Math_GMPz__Scalar;
        }

        ($x, $y) = (_mpz2mpfr($x), _mpq2mpfr($y));
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPz__Math_MPFR: {
        $x = _mpz2mpfr($x);
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPz__Math_MPC: {
        $x = _mpz2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        if (    Math::MPFR::Rmpfr_sgn($x) < 0
            and !Math::MPFR::Rmpfr_integer_p($y)
            and Math::MPFR::Rmpfr_number_p($y)) {
            $x = _mpfr2mpc($x);
            goto Math_MPC__Math_MPFR;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_pow($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Scalar: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        ($y < 0)
          ? Math::MPFR::Rmpfr_pow_si($r, $x, $y, $ROUND)
          : Math::MPFR::Rmpfr_pow_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_GMPq: {
        $y = _mpq2mpfr($y);
        goto Math_MPFR__Math_MPFR;
    }

  Math_MPFR__Math_GMPz: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_pow_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_pow($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Scalar: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        ($y < 0)
          ? Math::MPC::Rmpc_pow_si($r, $x, $y, $ROUND)
          : Math::MPC::Rmpc_pow_ui($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_pow_fr($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPz: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_pow_z($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_GMPq: {
        $y = _mpq2mpfr($y);
        goto Math_MPC__Math_MPFR;
    }
}

sub root {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__pow__($$x, __inv__($$y));
}

sub pow {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__pow__($$x, $$y);
}

sub tetration {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    # Base cases
    return ONE if $y->is_zero;    # a^^0 = 1
    return $x  if $y->is_one;     # a^^1 = a

    # Recursive case: a^^b = a^(a^^(b-1))
    $x->pow($x->tetration($y->dec));
}

sub ipow {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if (FAST_MODE and $y >= 0 and !ref($x) and $x >= 0) {

        if ($x > 0 and CORE::log($x) * $y < CORE::log(ULONG_MAX)) {
            my $r =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::powint($x, $y)
              : Math::Prime::Util::GMP::powint($x, $y);
            return bless \$r;
        }

        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_ui_pow_ui($r, $x, $y);
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    if (FAST_MODE and !ref($x) and $x == -1) {
        return (($y % 2 == 0) ? ONE : MONE);
    }

    $x = _any2mpz($x) // goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_pow_ui($r, $x, CORE::abs($y));

    if ($y < 0) {
        Math::GMPz::Rmpz_sgn($r) || goto &inf;    # 0^(-y) = Inf
        Math::GMPz::Rmpz_div($r, $ONE, $r);
    }

    bless \$r;
}

*powint = \&ipow;

sub ipow2 {
    my ($n) = @_;

    $n = $$n;
    $n = _any2si($n) // (goto &nan) if ref($n);

    ($n == 0) and return ONE;
    ($n == 1) and return TWO;
    ($n < 0)  and return ZERO;

    if (CORE::log(2) * $n < CORE::log(ULONG_MAX)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powint(2, $n)
          : Math::Prime::Util::GMP::powint(2, $n);
        return bless \$r;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_setbit($r, $n);
    bless \$r;
}

sub ipow10 {
    my ($n) = @_;

    $n = $$n;
    $n = _any2si($n) // (goto &nan) if ref($n);

    ($n == 0) and return ONE;
    ($n == 1) and return TEN;
    ($n < 0)  and return ZERO;

    if (CORE::log(10) * $n < CORE::log(ULONG_MAX)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powint(10, $n)
          : Math::Prime::Util::GMP::powint(10, $n);
        return bless \$r;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_ui_pow_ui($r, 10, $n);
    bless \$r;
}

sub __log2__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Complex for x < 0
        if (Math::MPFR::Rmpfr_sgn($x) < 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_log2($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));

        if ($MPC_VERSION >= 66560) {    # available only in mpc>=1.4.0
            Math::MPC::Rmpc_log2($r, $x, $ROUND);
        }
        else {
            my $ln2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_const_log2($ln2, $ROUND);
            Math::MPC::Rmpc_log($r, $x, $ROUND);
            Math::MPC::Rmpc_div_fr($r, $r, $ln2, $ROUND);
        }
        return $r;
    }
}

sub __log10__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Complex for x < 0
        if (Math::MPFR::Rmpfr_sgn($x) < 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_log10($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));

        if ($MPC_VERSION >= 65536) {    # available only in mpc>=1.0.0
            Math::MPC::Rmpc_log10($r, $x, $ROUND);
        }
        else {
            my $ln10 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_set_ui($ln10, 10, $ROUND);
            Math::MPFR::Rmpfr_log($ln10, $ln10, $ROUND);
            Math::MPC::Rmpc_log($r, $x, $ROUND);
            Math::MPC::Rmpc_div_fr($r, $r, $ln10, $ROUND);
        }

        return $r;
    }
}

sub __log__ {
    my ($x) = @_;

    goto($DISPATCH_TAG{ref($x)});

    #
    ## MPFR
    #
  Math_MPFR: {

        # Complex for x < 0
        if (Math::MPFR::Rmpfr_sgn($x) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_log($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_log($r, $x, $ROUND);
        return $r;
    }

    #
    ## MPC
    #
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_log($r, $x, $ROUND);
        return $r;
    }
}

sub log {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        bless \__div__(__log__(_any2mpfr_mpc($$x)), __log__(_any2mpfr_mpc($$y)));
    }
    else {
        bless \__log__(_any2mpfr_mpc($$x));
    }
}

sub ln {
    my ($x) = @_;
    bless \__log__(_any2mpfr_mpc($$x));
}

sub log2 {
    my ($x) = @_;
    bless \__log2__(_any2mpfr_mpc($$x));
}

sub log10 {
    my ($x) = @_;
    bless \__log10__(_any2mpfr_mpc($$x));
}

sub __ilog__ {
    my ($x, $y) = @_;

    if (ref($y) eq 'Math::GMPz' and Math::GMPz::Rmpz_fits_ulong_p($y)) {
        $y = Math::GMPz::Rmpz_get_ui($y);
    }

    # ilog(x, y <= 1) = NaN
    $y <= 1 and return;

    # ilog(x <= 0, y) = NaN
    Math::GMPz::Rmpz_sgn($x) <= 0 and return;

    # ilog(x,y) = 0, when y > x
    (ref($y) ? Math::GMPz::Rmpz_cmp($x, $y) : Math::GMPz::Rmpz_cmp_ui($x, $y)) >= 0
      or return 0;

    if (!ref($y)) {

        # Return faster for y = 2
        if ($y == 2) {
            return Math::GMPz::Rmpz_sizeinbase($x, 2) - 1;
        }

        if (FAST_MODE and Math::GMPz::Rmpz_fits_ulong_p($x)) {
            return (
                    HAS_PRIME_UTIL
                    ? Math::Prime::Util::logint(Math::GMPz::Rmpz_get_ui($x), $y)
                    : Math::Prime::Util::GMP::logint(Math::GMPz::Rmpz_get_ui($x), $y)
                   );
        }
    }

    # Return faster for y <= 62
    if ($y <= 62) {

        # NOTE: size is always exact for base = 2.
        my $e = (Math::GMPz::Rmpz_sizeinbase($x, $y) || return) - 1;

        if ($y != 2 and $e > 0) {
            state $t = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_ui_pow_ui($t, $y, $e);
            Math::GMPz::Rmpz_cmp($t, $x) > 0 and --$e;
        }

        return $e;
    }

    # Make sure `y` is a Math::GMPz object
    $y = Math::GMPz::Rmpz_init_set_ui($y) if !ref($y);

    my $e = 0;

    state $t    = Math::GMPz::Rmpz_init_nobless();
    state $logx = Math::MPFR::Rmpfr_init2_nobless(64);
    state $logy = Math::MPFR::Rmpfr_init2_nobless(64);

    Math::MPFR::Rmpfr_set_z($logx, $x, $round_z);
    Math::MPFR::Rmpfr_set_z($logy, $y, $round_z);
    Math::MPFR::Rmpfr_log($logx, $logx, $round_z);
    Math::MPFR::Rmpfr_log($logy, $logy, $round_z);
    Math::MPFR::Rmpfr_div($logx, $logx, $logy, $round_z);

    if (Math::MPFR::Rmpfr_fits_ulong_p($logx, $round_z)) {
        $e = Math::MPFR::Rmpfr_get_ui($logx, $round_z) - 1;
        Math::GMPz::Rmpz_pow_ui($t, $y, $e + 1);
    }
    else {
        Math::GMPz::Rmpz_set($t, $y);
    }

    for (; Math::GMPz::Rmpz_cmp($t, $x) <= 0 ; Math::GMPz::Rmpz_mul($t, $t, $y)) {
        ++$e;
    }

    return $e;
}

sub ilog {
    my ($x, $y) = @_;
    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);

        $x = $$x;
        $y = $$y;

        if (FAST_MODE and !ref($x) and !ref($y) and $x > 0 and $y > 1) {

            if ($y > $x) {
                return ZERO;
            }

            my $r =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::logint($x, $y)
              : Math::Prime::Util::GMP::logint($x, $y);
            return bless \$r;
        }

        _set_int(__ilog__((_any2mpz($x) // goto &nan), (_any2mpz($y) // goto &nan)) // goto &nan);
    }
    else {
        bless \(_any2mpz(__log__(_any2mpfr_mpc($$x))) // goto &nan);
    }
}

*logint = \&ilog;

sub ilog2 {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x) and $x > 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::logint($x, 2)
          : Math::Prime::Util::GMP::logint($x, 2);
        return bless \$r;
    }

    _set_int(__ilog__((_any2mpz($x) // goto &nan), 2) // goto &nan);
}

sub ilog10 {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x) and $x > 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::logint($x, 10)
          : Math::Prime::Util::GMP::logint($x, 10);
        return bless \$r;
    }

    _set_int(__ilog__((_any2mpz($x) // goto &nan), 10) // goto &nan);
}

sub msb {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;
    Math::GMPz::Rmpz_sgn($n) || return undef;

    _set_int(Math::GMPz::Rmpz_sizeinbase($n, 2) - 1);
}

sub lsb {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;
    Math::GMPz::Rmpz_sgn($n) || return undef;

    _set_int(Math::GMPz::Rmpz_scan1($n, 0));
}

sub fusc {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) >= 0 or goto &nan;

    if (Math::GMPz::Rmpz_even_p($n)) {
        $n = Math::GMPz::Rmpz_init_set($n);    # copy
        Math::GMPz::Rmpz_remove($n, $n, $TWO);
    }

    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n);

        my ($x, $y) = (1, 0);

        for (; $n > 0 ; $n >>= 1) {
            ($n & 1) ? ($y += $x) : ($x += $y);
        }

        return _set_int($y);
    }

    state $x = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($x, 1);

    my $y = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $i (0 .. Math::GMPz::Rmpz_sizeinbase($n, 2) - 1) {
        if (Math::GMPz::Rmpz_tstbit($n, $i)) {
            Math::GMPz::Rmpz_add($y, $y, $x);
        }
        else {
            Math::GMPz::Rmpz_add($x, $x, $y);
        }
    }

    bless \$y;
}

sub __lgrt__ {
    my ($c) = @_;

    $PREC = CORE::int($PREC) if ref($PREC);

    my $p = Math::MPFR::Rmpfr_init2($PREC);
    Math::MPFR::Rmpfr_set_str($p, '1e-' . CORE::int($PREC >> 2), 10, $ROUND);

    goto($DISPATCH_TAG{ref($c)});

  Math_MPFR: {

        # Return a complex number for x < e^(-1/e)
        if (Math::MPFR::Rmpfr_cmp_d($c, CORE::exp(-1 / CORE::exp(1))) < 0) {
            $c = _mpfr2mpc($c);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2($PREC);
        Math::MPFR::Rmpfr_log($r, $c, $ROUND);

        Math::MPFR::Rmpfr_set_ui((my $x = Math::MPFR::Rmpfr_init2($PREC)), 1, $ROUND);
        Math::MPFR::Rmpfr_set_ui((my $y = Math::MPFR::Rmpfr_init2($PREC)), 0, $ROUND);

        my $count = 0;
        my $tmp   = Math::MPFR::Rmpfr_init2($PREC);

        while (1) {
            Math::MPFR::Rmpfr_sub($tmp, $x, $y, $ROUND);
            Math::MPFR::Rmpfr_cmpabs($tmp, $p) <= 0 and last;

            Math::MPFR::Rmpfr_set($y, $x, $ROUND);

            Math::MPFR::Rmpfr_log($tmp, $x, $ROUND);
            Math::MPFR::Rmpfr_add_ui($tmp, $tmp, 1, $ROUND);

            Math::MPFR::Rmpfr_add($x, $x, $r, $ROUND);
            Math::MPFR::Rmpfr_div($x, $x, $tmp, $ROUND);
            last if ++$count > $PREC;
        }

        return $x;
    }

  Math_MPC: {
        my $d = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_log($d, $c, $ROUND);

        my $x = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_sqrt($x, $c, $ROUND);
        Math::MPC::Rmpc_add_ui($x, $x, 1, $ROUND);
        Math::MPC::Rmpc_log($x, $x, $ROUND);

        my $y = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_set_ui($y, 0, $ROUND);

        my $tmp = Math::MPC::Rmpc_init2($PREC);
        my $abs = Math::MPFR::Rmpfr_init2($PREC);

        my $count = 0;
        while (1) {
            Math::MPC::Rmpc_sub($tmp, $x, $y, $ROUND);

            Math::MPC::Rmpc_abs($abs, $tmp, $ROUND);
            Math::MPFR::Rmpfr_cmp($abs, $p) <= 0 and last;

            Math::MPC::Rmpc_set($y, $x, $ROUND);

            Math::MPC::Rmpc_log($tmp, $x, $ROUND);
            Math::MPC::Rmpc_add_ui($tmp, $tmp, 1, $ROUND);

            Math::MPC::Rmpc_add($x, $x, $d, $ROUND);
            Math::MPC::Rmpc_div($x, $x, $tmp, $ROUND);
            last if ++$count > $PREC;
        }

        return $x;
    }
}

sub lgrt {
    my ($x) = @_;
    bless \__lgrt__(_any2mpfr_mpc($$x));
}

sub __LambertW__ {
    my ($x) = @_;

    $PREC = CORE::int($PREC) if ref($PREC);

    my $p = Math::MPFR::Rmpfr_init2($PREC);
    Math::MPFR::Rmpfr_set_str($p, '1e-' . CORE::int($PREC >> 2), 10, $ROUND);

    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Return a complex number for x < -1/e
        if (Math::MPFR::Rmpfr_cmp_d($x, -1 / CORE::exp(1)) < 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        Math::MPFR::Rmpfr_set_ui((my $r = Math::MPFR::Rmpfr_init2($PREC)), 1, $ROUND);
        Math::MPFR::Rmpfr_set_ui((my $y = Math::MPFR::Rmpfr_init2($PREC)), 0, $ROUND);

        my $count = 0;
        my $tmp   = Math::MPFR::Rmpfr_init2($PREC);

        while (1) {
            Math::MPFR::Rmpfr_sub($tmp, $r, $y, $ROUND);
            Math::MPFR::Rmpfr_cmpabs($tmp, $p) <= 0 and last;

            Math::MPFR::Rmpfr_set($y, $r, $ROUND);

            Math::MPFR::Rmpfr_log($tmp, $r, $ROUND);
            Math::MPFR::Rmpfr_add_ui($tmp, $tmp, 1, $ROUND);

            Math::MPFR::Rmpfr_add($r, $r, $x, $ROUND);
            Math::MPFR::Rmpfr_div($r, $r, $tmp, $ROUND);
            last if ++$count > $PREC;
        }

        Math::MPFR::Rmpfr_log($r, $r, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_sqrt($r, $x, $ROUND);
        Math::MPC::Rmpc_add_ui($r, $r, 1, $ROUND);

        my $y = Math::MPC::Rmpc_init2($PREC);
        Math::MPC::Rmpc_set_ui($y, 0, $ROUND);

        my $tmp = Math::MPC::Rmpc_init2($PREC);
        my $abs = Math::MPFR::Rmpfr_init2($PREC);

        my $count = 0;
        while (1) {
            Math::MPC::Rmpc_sub($tmp, $r, $y, $ROUND);

            Math::MPC::Rmpc_abs($abs, $tmp, $ROUND);
            Math::MPFR::Rmpfr_cmp($abs, $p) <= 0 and last;

            Math::MPC::Rmpc_set($y, $r, $ROUND);

            Math::MPC::Rmpc_log($tmp, $r, $ROUND);
            Math::MPC::Rmpc_add_ui($tmp, $tmp, 1, $ROUND);

            Math::MPC::Rmpc_add($r, $r, $x, $ROUND);
            Math::MPC::Rmpc_div($r, $r, $tmp, $ROUND);
            last if ++$count > $PREC;
        }

        Math::MPC::Rmpc_log($r, $r, $ROUND);
        return $r;
    }
}

sub lambert_w {
    my ($x) = @_;
    bless \__LambertW__(_any2mpfr_mpc($$x));
}

*LambertW = \&lambert_w;

sub __exp__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_exp($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_exp($r, $x, $ROUND);
        return $r;
    }
}

sub exp {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        return bless \__pow__(_any2mpfr_mpc($$x), _any2mpfr_mpc($$y));
    }

    bless \__exp__(_any2mpfr_mpc($$x));
}

sub exp2 {
    my ($x) = @_;
    bless \__pow__(2, $$x);
}

sub exp10 {
    my ($x) = @_;
    bless \__pow__(10, $$x);
}

#
## sin / sinh / asin / asinh
#

sub __sin__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sin($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sin($r, $x, $ROUND);
        return $r;
    }
}

sub sin {
    my ($x) = @_;
    bless \__sin__(_any2mpfr_mpc($$x));
}

sub __sinh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sinh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sinh($r, $x, $ROUND);
        return $r;
    }
}

sub sinh {
    my ($x) = @_;
    bless \__sinh__(_any2mpfr_mpc($$x));
}

sub __asin__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Return a complex number for x < -1 or x > 1
        if (   Math::MPFR::Rmpfr_cmp_ui($x, 1) > 0
            or Math::MPFR::Rmpfr_cmp_si($x, -1) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_asin($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_asin($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_asin($r, $x, $ROUND);
        return $r;
    }
}

sub asin {
    my ($x) = @_;
    bless \__asin__(_any2mpfr_mpc($$x));
}

*arcsin = \&asin;

sub __asinh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_asinh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_asinh($r, $x, $ROUND);
        return $r;
    }
}

sub asinh {
    my ($x) = @_;
    bless \__asinh__(_any2mpfr_mpc($$x));
}

*arcsinh = \&asinh;

#
## cos / cosh / acos / acosh
#

sub __cos__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_cos($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_cos($r, $x, $ROUND);
        return $r;
    }
}

sub cos {
    my ($x) = @_;
    bless \__cos__(_any2mpfr_mpc($$x));
}

sub __cosh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_cosh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_cosh($r, $x, $ROUND);
        return $r;
    }
}

sub cosh {
    my ($x) = @_;
    bless \__cosh__(_any2mpfr_mpc($$x));
}

sub __acos__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Return a complex number for x < -1 or x > 1
        if (   Math::MPFR::Rmpfr_cmp_ui($x, 1) > 0
            or Math::MPFR::Rmpfr_cmp_si($x, -1) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_acos($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_acos($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_acos($r, $x, $ROUND);
        return $r;
    }
}

sub acos {
    my ($x) = @_;
    bless \__acos__(_any2mpfr_mpc($$x));
}

*arccos = \&acos;

sub __acosh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Return a complex number for x < 1
        if (Math::MPFR::Rmpfr_cmp_ui($x, 1) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_acosh($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_acosh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_acosh($r, $x, $ROUND);
        return $r;
    }
}

sub acosh {
    my ($x) = @_;
    bless \__acosh__(_any2mpfr_mpc($$x));
}

*arccosh = \&acosh;

#
## tan / tanh / atan / atanh
#

sub __tan__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_tan($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_tan($r, $x, $ROUND);
        return $r;
    }
}

sub tan {
    my ($x) = @_;
    bless \__tan__(_any2mpfr_mpc($$x));
}

sub __tanh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_tanh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_tanh($r, $x, $ROUND);
        return $r;
    }
}

sub tanh {
    my ($x) = @_;
    bless \__tanh__(_any2mpfr_mpc($$x));
}

sub __atan__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_atan($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_atan($r, $x, $ROUND);
        return $r;
    }
}

sub atan {
    my ($x) = @_;
    bless \__atan__(_any2mpfr_mpc($$x));
}

*arctan = \&atan;

sub __atanh__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {

        # Return a complex number for x < -1 or x > 1
        if (   Math::MPFR::Rmpfr_cmp_ui($x, +1) > 0
            or Math::MPFR::Rmpfr_cmp_si($x, -1) < 0) {
            my $r = _mpfr2mpc($x);
            Math::MPC::Rmpc_atanh($r, $r, $ROUND);
            return $r;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_atanh($r, $x, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_atanh($r, $x, $ROUND);
        return $r;
    }
}

sub atanh {
    my ($x) = @_;
    bless \__atanh__(_any2mpfr_mpc($$x));
}

*arctanh = \&atanh;

sub __atan2__ {
    my ($x, $y) = @_;
    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_atan2($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }

    #
    ## atan2(x, y) = -i * log((y + x*i) / sqrt(x^2 + y^2))
    #
  Math_MPC__Math_MPC: {
        my $r = Math::MPC::Rmpc_init2($PREC);

        Math::MPC::Rmpc_mul_i($r, $x, 1, $ROUND);
        Math::MPC::Rmpc_add($r, $r, $y, $ROUND);

        my $t1 = Math::MPC::Rmpc_init2($PREC);
        my $t2 = Math::MPC::Rmpc_init2($PREC);

        Math::MPC::Rmpc_sqr($t1, $x, $ROUND);
        Math::MPC::Rmpc_sqr($t2, $y, $ROUND);
        Math::MPC::Rmpc_add($t1, $t1, $t2, $ROUND);
        Math::MPC::Rmpc_sqrt($t1, $t1, $ROUND);

        Math::MPC::Rmpc_div($r, $r, $t1, $ROUND);
        Math::MPC::Rmpc_log($r, $r, $ROUND);
        Math::MPC::Rmpc_mul_i($r, $r, -1, $ROUND);

        return $r;
    }
}

sub atan2 {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__atan2__(_any2mpfr_mpc($$x), _any2mpfr_mpc($$y));
}

sub arg {
    my ($x) = @_;
    $x->imag->atan2($x->real);
}

*phase = \&arg;
*angle = \&arg;

#
## sec / sech / asec / asech
#

sub __sec__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sec($r, $x, $ROUND);
        return $r;
    }

    # sec(x) = 1/cos(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_cos($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub sec {
    my ($x) = @_;
    bless \__sec__(_any2mpfr_mpc($$x));
}

sub __sech__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sech($r, $x, $ROUND);
        return $r;
    }

    # sech(x) = 1/cosh(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_cosh($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub sech {
    my ($x) = @_;
    bless \__sech__(_any2mpfr_mpc($$x));
}

sub __asec__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # asec(x) = acos(1/x)
  Math_MPFR: {

        # Return a complex number for x > -1 and x < 1
        if (    Math::MPFR::Rmpfr_cmp_ui($x, 1) < 0
            and Math::MPFR::Rmpfr_cmp_si($x, -1) > 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_acos($r, $r, $ROUND);
        return $r;
    }

    # asec(x) = acos(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_acos($r, $r, $ROUND);
        return $r;
    }
}

sub asec {
    my ($x) = @_;
    bless \__asec__(_any2mpfr_mpc($$x));
}

*arcsec = \&asec;

sub __asech__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # asech(x) = acosh(1/x)
  Math_MPFR: {

        # Return a complex number for x < 0 or x > 1
        if (   Math::MPFR::Rmpfr_cmp_ui($x, 1) > 0
            or Math::MPFR::Rmpfr_cmp_ui($x, 0) < 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_acosh($r, $r, $ROUND);
        return $r;
    }

    # asech(x) = acosh(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_acosh($r, $r, $ROUND);
        return $r;
    }
}

sub asech {
    my ($x) = @_;
    bless \__asech__(_any2mpfr_mpc($$x));
}

*arcsech = \&asech;

#
## csc / csch / acsc / acsch
#

sub __csc__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_csc($r, $x, $ROUND);
        return $r;
    }

    # csc(x) = 1/sin(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sin($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub csc {
    my ($x) = @_;
    bless \__csc__(_any2mpfr_mpc($$x));
}

sub __csch__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_csch($r, $x, $ROUND);
        return $r;
    }

    # csch(x) = 1/sinh(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sinh($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub csch {
    my ($x) = @_;
    bless \__csch__(_any2mpfr_mpc($$x));
}

sub __acsc__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # acsc(x) = asin(1/x)
  Math_MPFR: {

        # Return a complex number for x > -1 and x < 1
        if (    Math::MPFR::Rmpfr_cmp_ui($x, 1) < 0
            and Math::MPFR::Rmpfr_cmp_si($x, -1) > 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_asin($r, $r, $ROUND);
        return $r;
    }

    # acsc(x) = asin(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_asin($r, $r, $ROUND);
        return $r;
    }
}

sub acsc {
    my ($x) = @_;
    bless \__acsc__(_any2mpfr_mpc($$x));
}

*arccsc = \&acsc;

sub __acsch__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # acsch(x) = asinh(1/x)
  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_asinh($r, $r, $ROUND);
        return $r;
    }

    # acsch(x) = asinh(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_asinh($r, $r, $ROUND);
        return $r;
    }
}

sub acsch {
    my ($x) = @_;
    bless \__acsch__(_any2mpfr_mpc($$x));
}

*arccsch = \&acsch;

#
## cot / coth / acot / acoth
#

sub __cot__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_cot($r, $x, $ROUND);
        return $r;
    }

    # cot(x) = 1/tan(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_tan($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub cot {
    my ($x) = @_;
    bless \__cot__(_any2mpfr_mpc($$x));
}

sub __coth__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_coth($r, $x, $ROUND);
        return $r;
    }

    # coth(x) = 1/tanh(x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $t = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_tanh($r, $x, $ROUND);
        Math::MPC::Rmpc_set_ui($t, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $t, $r, $ROUND);
        return $r;
    }
}

sub coth {
    my ($x) = @_;
    bless \__coth__(_any2mpfr_mpc($$x));
}

sub __acot__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # acot(x) = atan(1/x)
  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_atan($r, $r, $ROUND);
        return $r;
    }

    # acot(x) = atan(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_atan($r, $r, $ROUND);
        return $r;
    }
}

sub acot {
    my ($x) = @_;
    bless \__acot__(_any2mpfr_mpc($$x));
}

*arccot = \&acot;

sub __acoth__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

    # acoth(x) = atanh(1/x)
  Math_MPFR: {

        # Return a complex number for x > -1 and x < 1
        if (    Math::MPFR::Rmpfr_cmp_ui($x, 1) < 0
            and Math::MPFR::Rmpfr_cmp_si($x, -1) > 0) {
            $x = _mpfr2mpc($x);
            goto Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ui_div($r, 1, $x, $ROUND);
        Math::MPFR::Rmpfr_atanh($r, $r, $ROUND);
        return $r;
    }

    # acoth(x) = atanh(1/x)
  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($r, 1, $ROUND);
        Math::MPC::Rmpc_div($r, $r, $x, $ROUND);
        Math::MPC::Rmpc_atanh($r, $r, $ROUND);
        return $r;
    }
}

sub acoth {
    my ($x) = @_;
    bless \__acoth__(_any2mpfr_mpc($$x));
}

*arccoth = \&acoth;

sub __cis__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui_fr($r, 0, $x, $ROUND);
        Math::MPC::Rmpc_exp($r, $r, $ROUND);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_mul_i($r, $x, 1, $ROUND);
        Math::MPC::Rmpc_exp($r, $r, $ROUND);
        return $r;
    }
}

sub cis {
    my ($x) = @_;
    bless \__cis__(_any2mpfr_mpc($$x));
}

sub roots_of_unity {
    my ($n) = @_;
    $n = _any2ui($$n) || return _array();

    my $one = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set_ui($one, 1, $ROUND);
    my @roots = (bless \$one);

    my $cos = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $sin = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    my $pi = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_pi($pi, $ROUND);

    foreach my $i (1 .. $n - 1) {

        # r = exp(2*pi / n * i)
        Math::MPFR::Rmpfr_mul_ui($sin, $pi, 2 * $i, $ROUND);
        Math::MPFR::Rmpfr_div_ui($sin, $sin, $n, $ROUND);
        Math::MPFR::Rmpfr_sin_cos($sin, $cos, $sin, $ROUND);

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr_fr($r, $cos, $sin, $ROUND);
        push @roots, bless \$r;
    }

    _array(\@roots);
}

sub __sin_cos__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Math_MPFR: {
        my $cos = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $sin = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPFR::Rmpfr_sin_cos($sin, $cos, $x, $ROUND);

        return ($sin, $cos);
    }

  Math_MPC: {
        my $cos = Math::MPC::Rmpc_init2(CORE::int($PREC));
        my $sin = Math::MPC::Rmpc_init2(CORE::int($PREC));

        Math::MPC::Rmpc_sin_cos($sin, $cos, $x, $ROUND, $ROUND);

        return ($sin, $cos);
    }
}

sub sin_cos {
    my ($x) = @_;
    my ($sin, $cos) = __sin_cos__(_any2mpfr_mpc($$x));
    ((bless \$sin), (bless \$cos));
}

#
## Special functions
#

sub __agm__ {
    my ($x, $y) = @_;
    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

  Math_MPFR__Math_MPFR: {
        if (   Math::MPFR::Rmpfr_sgn($x) < 0
            or Math::MPFR::Rmpfr_sgn($y) < 0) {
            ($x, $y) = (_mpfr2mpc($x), _mpfr2mpc($y));
            goto Math_MPC__Math_MPC;
        }

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_agm($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPC: {

        # agm(0,  x) = 0
        Math::MPC::Rmpc_cmp_si_si($x, 0, 0) || return $x;

        # agm(x, 0) = 0
        Math::MPC::Rmpc_cmp_si_si($y, 0, 0) || return $y;

        $PREC = CORE::int($PREC) if ref($PREC);

        my $a0 = Math::MPC::Rmpc_init2($PREC);
        my $g0 = Math::MPC::Rmpc_init2($PREC);

        my $a1 = Math::MPC::Rmpc_init2($PREC);
        my $g1 = Math::MPC::Rmpc_init2($PREC);

        my $t = Math::MPC::Rmpc_init2($PREC);

        Math::MPC::Rmpc_set($a0, $x, $ROUND);
        Math::MPC::Rmpc_set($g0, $y, $ROUND);

        my $count = 0;
        {
            Math::MPC::Rmpc_add($a1, $a0, $g0, $ROUND);
            Math::MPC::Rmpc_div_2ui($a1, $a1, 1, $ROUND);

            Math::MPC::Rmpc_mul($g1, $a0, $g0, $ROUND);
            Math::MPC::Rmpc_add($t, $a0, $g0, $ROUND);
            Math::MPC::Rmpc_sqr($t, $t, $ROUND);
            Math::MPC::Rmpc_cmp_si_si($t, 0, 0) || return $t;
            Math::MPC::Rmpc_div($g1, $g1, $t, $ROUND);
            Math::MPC::Rmpc_sqrt($g1, $g1, $ROUND);
            Math::MPC::Rmpc_add($t, $a0, $g0, $ROUND);
            Math::MPC::Rmpc_mul($g1, $g1, $t, $ROUND);

            if (Math::MPC::Rmpc_cmp($a0, $a1) and ++$count < $PREC) {
                Math::MPC::Rmpc_set($a0, $a1, $ROUND);
                Math::MPC::Rmpc_set($g0, $g1, $ROUND);
                redo;
            }
        }

        return $g0;
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }
}

sub agm {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__agm__(_any2mpfr_mpc($$x), _any2mpfr_mpc($$y));
}

sub __hypot__ {
    my ($x, $y) = @_;

    # hypot(x, y) = sqrt(x^2 + y^2)

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

  Math_MPFR__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_hypot($r, $x, $y, $ROUND);
        return $r;
    }

  Math_MPFR__Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($r, $y, $ROUND);
        Math::MPFR::Rmpfr_hypot($r, $r, $x, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($r, $x, $ROUND);
        Math::MPFR::Rmpfr_hypot($r, $r, $y, $ROUND);
        return $r;
    }

  Math_MPC__Math_MPC: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($r, $x, $ROUND);
        my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($t, $y, $ROUND);
        Math::MPFR::Rmpfr_hypot($r, $r, $t, $ROUND);
        return $r;
    }
}

sub hypot {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__hypot__(_any2mpfr_mpc($$x), _any2mpfr_mpc($$y));
}

sub gamma {
    my ($x) = @_;

    ref($_[0]) || goto &EulerGamma;

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_gamma($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*Γ = \&gamma;

sub lngamma {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_lngamma($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*gamma_log = \&lngamma;

sub lgamma {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_lgamma($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*gamma_abs_log = \&lgamma;

sub digamma {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_digamma($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*Ψ = \&digamma;

#
## beta(x, y) = gamma(x)*gamma(y) / gamma(x+y)
#
sub beta {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpfr($$x);
    $y = _any2mpfr($$y);

    state $has_beta = Math::MPFR::MPFR_VERSION_MAJOR() >= 4;

    if ($has_beta) {    # available since mpfr-4.0.0
        my $r = Math::MPFR::Rmpfr_init2($PREC);
        Math::MPFR::Rmpfr_beta($r, $x, $y, $ROUND);
        return bless \$r;
    }

    my $t1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));    # gamma(x+y)
    my $t2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));    # gamma(y)

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPFR::Rmpfr_add($t1, $x, $y, $ROUND);
    Math::MPFR::Rmpfr_gamma($t1, $t1, $ROUND);
    Math::MPFR::Rmpfr_gamma($r,  $x,  $ROUND);
    Math::MPFR::Rmpfr_gamma($t2, $y,  $ROUND);
    Math::MPFR::Rmpfr_mul($r, $r, $t2, $ROUND);
    Math::MPFR::Rmpfr_div($r, $r, $t1, $ROUND);

    bless \$r;
}

#
## eta(s) = (1 - 2^(1-s)) * zeta(s)
#
sub eta {
    my ($x) = @_;

    $x = _any2mpfr($$x);

    my $x_is_int = Math::MPFR::Rmpfr_integer_p($x);
    my $r        = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    # Special case for eta(1) = log(2)
    if ($x_is_int and Math::MPFR::Rmpfr_cmp_ui($x, 1) == 0) {
        Math::MPFR::Rmpfr_const_log2($r, $ROUND);
        return bless \$r;
    }

    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPFR::Rmpfr_ui_sub($r, 1, $x, $ROUND);
    Math::MPFR::Rmpfr_ui_pow($r, 2, $r, $ROUND);
    Math::MPFR::Rmpfr_ui_sub($r, 1, $r, $ROUND);

    if ($x_is_int and Math::MPFR::Rmpfr_fits_ulong_p($x, $ROUND)) {
        Math::MPFR::Rmpfr_zeta_ui($t, Math::MPFR::Rmpfr_get_ui($x, $ROUND), $ROUND);
    }
    else {
        Math::MPFR::Rmpfr_zeta($t, $x, $ROUND);
    }

    Math::MPFR::Rmpfr_mul($r, $r, $t, $ROUND);

    bless \$r;
}

*η = \&eta;

sub zeta {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    my $f = _any2mpfr($$x);
    if (    Math::MPFR::Rmpfr_integer_p($f)
        and Math::MPFR::Rmpfr_fits_ulong_p($f, $ROUND)) {
        Math::MPFR::Rmpfr_zeta_ui($r, Math::MPFR::Rmpfr_get_ui($f, $ROUND), $ROUND);
    }
    else {
        Math::MPFR::Rmpfr_zeta($r, $f, $ROUND);
    }
    bless \$r;
}

*ζ = \&zeta;

sub _secant_numbers {
    my ($n) = @_;

    state @cache;

    if ($n <= $#cache) {
        return @cache;
    }

    $n <<= 1 if ($n <= 512);

    my @S = (Math::GMPz::Rmpz_init_set_ui(1));

    foreach my $k (1 .. $n) {
        Math::GMPz::Rmpz_mul_ui($S[$k] = Math::GMPz::Rmpz_init(), $S[$k - 1], $k);
    }

    foreach my $k (1 .. $n) {
        foreach my $j ($k + 1 .. $n) {
            Math::GMPz::Rmpz_addmul_ui($S[$j], $S[$j - 1], ($j - $k) * ($j - $k + 2));
        }
    }

    push @cache, @S[@cache .. (@S <= 1024 ? $#S : 1024)];

    return @S;
}

sub _tangent_numbers {
    my ($n) = @_;

    state @cache;

    if ($n <= $#cache) {
        return @cache;
    }

    $n <<= 1 if ($n <= 512);

    my @T = (Math::GMPz::Rmpz_init_set_ui(1));

    foreach my $k (1 .. $n) {
        Math::GMPz::Rmpz_mul_ui($T[$k] = Math::GMPz::Rmpz_init(), $T[$k - 1], $k);
    }

    foreach my $k (1 .. $n) {
        foreach my $j ($k .. $n) {
            Math::GMPz::Rmpz_mul_ui($T[$j], $T[$j], $j - $k + 2);
            Math::GMPz::Rmpz_addmul_ui($T[$j], $T[$j - 1], $j - $k);
        }
    }

    push @cache, @T[@cache .. (@T <= 1024 ? $#T : 1024)];

    return @T;
}

sub _bernoulli_numbers {
    my ($n) = @_;

    $n = ($n >> 1) + 1;

    state @cache;

    if ($n <= $#cache) {
        return @cache;
    }

    my @B;
    my @T = _tangent_numbers($n);

    my $t = Math::GMPz::Rmpz_init();

    foreach my $k (scalar(@cache) .. 2 * @T) {

        $k % 2 == 0 or $k == 1 or next;

        my $q = Math::GMPq::Rmpq_init();

        if ($k == 0) {
            Math::GMPq::Rmpq_set_ui($q, 1, 1);
            $B[$k] = $q;
            next;
        }

        if ($k == 1) {
            Math::GMPq::Rmpq_set_si($q, -1, 2);
            $B[$k] = $q;
            next;
        }

        # T_k
        Math::GMPz::Rmpz_mul_ui($t, $T[($k >> 1) - 1], $k);
        Math::GMPz::Rmpz_neg($t, $t) if ((($k >> 1) - 1) & 1);
        Math::GMPq::Rmpq_set_z($q, $t);

        # (2^k - 1) * 2^k
        Math::GMPz::Rmpz_set_ui($t, 0);
        Math::GMPz::Rmpz_setbit($t, $k);
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        Math::GMPz::Rmpz_mul_2exp($t, $t, $k);

        # B_k = q
        Math::GMPq::Rmpq_div_z($q, $q, $t);

        $B[($k >> 1) + 1] = $q;
    }

    push @cache, @B[@cache .. (@B <= 1024 ? $#B : 1024)];

    return (@cache, (@B > @cache ? @B[@cache .. $#B] : ()));
}

sub bernoulli_numbers {
    my ($n) = @_;

    $n = _any2ui($$n) // return _array();

    my @bern = _bernoulli_numbers($n);

    $#bern = ($n >> 1) + 1;

    for (my $i = 3 ; $i <= $n ; $i += 2) {
        splice(@bern, $i, 0, 0);
    }

    $#bern = $n;
    _array([map { bless \$_ } @bern]);
}

sub bernoulli_polynomial {
    my ($n, $x) = @_;

    #
    ## B_n(x) = Sum_{k=0..n} binomial(n, k) * bernoulli(n-k) * x^k
    #

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
        $x = $$x;
    }
    else {
        $polynomial = 1;
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
    }

    $n = _any2ui($$n) // goto &nan;

    if ($polynomial and $n >= 6000 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("bernpol($n)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    if (!$polynomial and __eq__($x, 0)) {
        return _set_int($n)->bernfrac;
    }
    elsif (!$polynomial and __eq__($x, 1)) {
        if ($n == 1) {
            state $r = _str2obj('1/2');
            return bless \$r;
        }
        return _set_int($n)->bernfrac;
    }

    my @B = _bernoulli_numbers($n);

    my $u = $n + 1;
    my $z = Math::GMPz::Rmpz_init();
    my $q = Math::GMPq::Rmpq_init();

    my @terms;

    foreach my $k (0 .. $n) {

        --$u & 1 and $u > 1 and next;    # B_n = 0 for odd n > 1

        Math::GMPz::Rmpz_bin_uiui($z, $n, $k);
        Math::GMPq::Rmpq_mul_z($q, $u <= 1 ? $B[$u] : $B[($u >> 1) + 1], $z);

        if ($polynomial) {
            push @terms, $x->pow(_set_int($k))->mul(bless \$q);
        }
        else {
            push @terms, __mul__($q, ($k ? __pow__($x, $k) : 1));
        }
    }

    $polynomial
      ? _binsplit([CORE::reverse @terms], \&Sidef::Types::Number::Polynomial::add)
      : (bless \_binsplit([CORE::reverse @terms], \&__add__));
}

sub bernfrac {
    my ($n, $x) = @_;

    defined($x) && goto &bernoulli_polynomial;

    $n = _any2ui($$n) // goto &nan;

    $n == 0 and return ONE;
    $n > 1 and $n % 2 and return ZERO;    # Bn=0 for odd n>1

    if ($n > 1 and $n < 512) {
        return bless \((_bernoulli_numbers($n))[($n >> 1) + 1]);
    }

    if ($n == 1) {
        state $q = _str2obj('-1/2');
        return bless \$q;
    }

    if ($n >= 2000 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("bernfrac($n)")) {
            my $q = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_set_str($q, $res, 10);
            return bless \$q;
        }
    }

    # Using bernfrac() from `Math::Prime::Util::GMP`
    my ($num, $den) = Math::Prime::Util::GMP::bernfrac($n);

    my $q = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_str($q, "$num/$den", 10);
    bless \$q;
}

*bern             = \&bernfrac;
*bernoulli        = \&bernfrac;
*Bernoulli        = \&bernfrac;
*BernoulliB       = \&bernfrac;
*bernoulli_number = \&bernfrac;

sub genocchi {
    my ($n) = @_;
    (TWO)->mul(ONE->sub((FOUR)->ipow($n)))->mul($n->add($n)->bernoulli);
}

sub faulhaber_polynomial {
    my ($n, $x) = @_;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
    }

    $n = $n->inc;
    $x = $x->inc;

    $n->bernoulli_polynomial($x)->sub($n->bernfrac)->div($n);
}

sub euler_polynomial {
    my ($n, $x) = @_;

    #
    ## E_n(x) = Sum_{k=0..n} binomial(n, n-k) * euler_number(n-k) / 2^(n-k) * (x - 1/2)^k
    #

    my $polynomial = 0;
    my $orig_x;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
        $x = $$x;
        $x = __dec__(__add__($x, $x));    # x = 2*x - 1
    }
    else {
        $polynomial = 1;
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
        $orig_x = $x;
        $x      = $x->add($x)->dec;
    }

    $n = _any2ui($$n) // goto &nan;

    if ($polynomial and $n >= 10 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("eulerpol($n)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($orig_x);
        }
    }

    my @S = _secant_numbers(($n >> 1) + 1);

    my $u = $n + 1;
    my $z = Math::GMPz::Rmpz_init();

    my @terms;

    foreach my $k (0 .. $n) {
        --$u & 1 and next;    # E_n = 0 for all odd n

        Math::GMPz::Rmpz_bin_uiui($z, $n, $u);
        Math::GMPz::Rmpz_mul($z, $z, $S[$u >> 1]);
        Math::GMPz::Rmpz_neg($z, $z) if (($u >> 1) & 1);

        if ($polynomial) {
            push @terms, $x->pow(_set_int($k))->mul(bless \$z);
        }
        else {
            push @terms, ($k ? __mul__(__pow__($x, $k), $z) : Math::GMPz::Rmpz_init_set($z));
        }
    }

    if ($polynomial) {
        my $sum = _binsplit(\@terms, \&Sidef::Types::Number::Polynomial::add);
        Math::GMPz::Rmpz_set_ui($z, 0);
        Math::GMPz::Rmpz_setbit($z, $n);
        return $sum->div(bless \$z);
    }

    my $sum = _binsplit(\@terms, \&__add__);
    Math::GMPz::Rmpz_set_ui($z, 0);
    Math::GMPz::Rmpz_setbit($z, $n);
    bless \__div__($sum, $z);
}

sub euler {
    my ($n, $x) = @_;

    ref($n) || goto &EulerGamma;
    defined($x) && goto &euler_polynomial;

    $n = _any2ui($$n) // goto &nan;

    $n & 1 and return ZERO;    # E_n = 0 for all odd indices

    if ($n >= 1500 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("eulerfrac($n)")) {
            my $z = Math::GMPz::Rmpz_init_set_str($res, 10);
            return bless \$z;
        }
    }

    my $e = Math::GMPz::Rmpz_init_set((_secant_numbers(($n >> 1) + 1))[$n >> 1]);
    Math::GMPz::Rmpz_neg($e, $e) if (($n >> 1) & 1);
    bless \$e;
}

*Euler        = \&euler;
*euler_number = \&euler;

sub euler_numbers {
    my ($n) = @_;

    $n = _any2ui($$n) // return _array();

    my @euler = _secant_numbers(($n >> 1) + 1);

    $#euler = ($n >> 1) + 1;

    for (my $i = 1 ; $i <= $#euler ; $i += 2) {
        my $t = Math::GMPz::Rmpz_init_set($euler[$i]);
        Math::GMPz::Rmpz_neg($t, $t);
        splice(@euler, $i, 1, $t);
    }

    for (my $i = 1 ; $i <= $n ; $i += 2) {
        splice(@euler, $i, 0, 0);
    }

    $#euler = $n;
    _array([map { bless \$_ } @euler]);
}

sub _fubini_numbers {
    my ($n) = @_;

    # Cache for the sequence of Fubini numbers
    state $F = [Math::GMPz::Rmpz_init_set_ui(1)];

    # State cache for the dynamic programming row
    state $row = [Math::GMPz::Rmpz_init_set_ui(1)];

    foreach my $i ($#{$F} + 1 .. $n) {

        # Expand the row array for the current iteration
        push @{$row}, Math::GMPz::Rmpz_init_set_ui(0);

        # Process the row backwards to prevent overwriting values we still need
        for (my $k = $i ; $k > 0 ; $k--) {
            Math::GMPz::Rmpz_add($row->[$k], $row->[$k], $row->[$k - 1]);
            Math::GMPz::Rmpz_mul_ui($row->[$k], $row->[$k], $k);
        }
        Math::GMPz::Rmpz_set_ui($row->[0], 0);

        # Sum the current row to get the i-th Fubini number
        my $sum = ($F->[$i] = Math::GMPz::Rmpz_init_set_ui(0));
        foreach my $k (1 .. $i) {
            Math::GMPz::Rmpz_add($sum, $sum, $row->[$k]);
        }
    }

    return $F;
}

sub fubini_numbers {    # OEIS: A000670
    my ($n) = @_;
    $n = _any2ui($$n) // return _array();

    my @F = @{_fubini_numbers($n)};
    $#F = $n;
    _array([map { bless \$_ } @F]);
}

sub fubini {    # OEIS: A000670
    my ($n) = @_;
    $n = _any2ui($$n) // goto &nan;

    my $r = _fubini_numbers($n)->[$n];
    bless \$r;
}

*Fubini        = \&fubini;
*fubini_number = \&fubini;

sub secant_number {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my @E = _secant_numbers($n);
    bless \(my $value = Math::GMPz::Rmpz_init_set($E[$n]));
}

sub tangent_number {
    my ($n) = @_;

    #
    ## T_n = 2^(2*n) * (2^(2*n) - 1) * abs(bernoulli(2*n)) / (2*n)
    #

    $n = _any2ui($$n) // goto &nan;
    $n || return ZERO;
    $n <<= 1;

    my ($num, $den) = Math::Prime::Util::GMP::bernfrac($n);

    $num = Math::GMPz::Rmpz_init_set_str("$num", 10);
    $den = Math::GMPz::Rmpz_init_set_str("$den", 10);

    Math::GMPz::Rmpz_abs($num, $num) if !($n & 1);

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_setbit($r, $n);
    Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    Math::GMPz::Rmpz_mul_2exp($r, $r, $n);
    Math::GMPz::Rmpz_mul($r, $r, $num);
    Math::GMPz::Rmpz_mul_ui($den, $den, $n);
    Math::GMPz::Rmpz_divexact($r, $r, $den);
    bless \$r;
}

sub bernreal {
    my ($n_ref, $x_ref) = @_;

    my $n = _any2ui($$n_ref) // goto &nan;

    # Fast O(1) path for Bernoulli number B_n (when x is omitted)
    if (!defined($x_ref)) {
        $n == 0 and return ONE;
        $n == 1 and return do {
            my $half = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_set_d($half, -0.5, $ROUND);
            bless \$half;
        };
        $n % 2 and return ZERO;    # B_n = 0 for odd n > 1

        my $B_n = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $t   = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPFR::Rmpfr_zeta_ui($B_n, $n, $ROUND);      # zeta(n)
        Math::MPFR::Rmpfr_set_ui($t, $n + 1, $ROUND);
        Math::MPFR::Rmpfr_gamma($t, $t, $ROUND);          # gamma(n+1) = n!
        Math::MPFR::Rmpfr_mul($B_n, $B_n, $t, $ROUND);    # zeta(n) * n!

        Math::MPFR::Rmpfr_const_pi($t, $ROUND);           # pi
        Math::MPFR::Rmpfr_mul_ui($t, $t, 2, $ROUND);      # 2*pi
        Math::MPFR::Rmpfr_pow_ui($t, $t, $n, $ROUND);     # (2*pi)^n

        Math::MPFR::Rmpfr_div($B_n, $B_n, $t, $ROUND);    # (zeta(n)*n!) / (2*pi)^n
        Math::MPFR::Rmpfr_mul_ui($B_n, $B_n, 2, $ROUND);  # 2 * ...

        Math::MPFR::Rmpfr_neg($B_n, $B_n, $ROUND) if $n % 4 == 0;

        return bless \$B_n;
    }

    # Evaluate Bernoulli Polynomial B_n(x)
    my $x_fr = _any2mpfr($$x_ref) // goto &nan;

    my $sum  = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $term = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $B_j  = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $t    = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $z    = Math::GMPz::Rmpz_init();

    Math::MPFR::Rmpfr_set_ui($sum, 0, $ROUND);

    foreach my $j (0 .. $n) {
        if ($j == 0) {
            Math::MPFR::Rmpfr_set_ui($B_j, 1, $ROUND);
        }
        elsif ($j == 1) {
            Math::MPFR::Rmpfr_set_d($B_j, -0.5, $ROUND);
        }
        elsif ($j % 2 != 0) {
            next;    # B_j = 0 for odd j > 1
        }
        else {
            # Compute B_j using Zeta and Gamma
            Math::MPFR::Rmpfr_zeta_ui($B_j, $j, $ROUND);    # zeta(j)
            Math::MPFR::Rmpfr_set_ui($t, $j + 1, $ROUND);
            Math::MPFR::Rmpfr_gamma($t, $t, $ROUND);        # gamma(j+1)
            Math::MPFR::Rmpfr_mul($B_j, $B_j, $t, $ROUND);

            Math::MPFR::Rmpfr_const_pi($t, $ROUND);
            Math::MPFR::Rmpfr_mul_ui($t, $t, 2, $ROUND);
            Math::MPFR::Rmpfr_pow_ui($t, $t, $j, $ROUND);    # (2*pi)^j

            Math::MPFR::Rmpfr_div($B_j, $B_j, $t, $ROUND);
            Math::MPFR::Rmpfr_mul_ui($B_j, $B_j, 2, $ROUND);
            Math::MPFR::Rmpfr_neg($B_j, $B_j, $ROUND) if $j % 4 == 0;
        }

        # term = binom(n, j) * B_j
        Math::GMPz::Rmpz_bin_uiui($z, $n, $j);
        Math::MPFR::Rmpfr_mul_z($term, $B_j, $z, $ROUND);

        # term *= x^(n-j)
        if ($n - $j > 0) {
            Math::MPFR::Rmpfr_pow_ui($t, $x_fr, $n - $j, $ROUND);
            Math::MPFR::Rmpfr_mul($term, $term, $t, $ROUND);
        }

        Math::MPFR::Rmpfr_add($sum, $sum, $term, $ROUND);
    }

    return bless \$sum;
}

sub lnbernreal {
    my ($n_ref, $x_ref) = @_;

    # Fast O(1) path for ln(B_n) when x is omitted
    if (!defined($x_ref)) {
        my $n = _any2mpz($$n_ref) // goto &nan;
        (Math::GMPz::Rmpz_sgn($n) || return ZERO) < 0 and goto &nan;

        if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
            my $L = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_const_log2($L, $ROUND);
            Math::MPFR::Rmpfr_neg($L, $L, $ROUND);

            my $pi = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
            Math::MPFR::Rmpfr_const_pi($pi, $ROUND);

            my $c = Math::MPC::Rmpc_init2(CORE::int($PREC));
            Math::MPC::Rmpc_set_fr_fr($c, $L, $pi, $ROUND);
            return bless \$c;
        }

        Math::GMPz::Rmpz_odd_p($n) && goto &ninf;

        my $L  = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $t  = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $pi = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPFR::Rmpfr_const_log2($t, $ROUND);
        Math::MPFR::Rmpfr_set($L, $t, $ROUND);

        Math::MPFR::Rmpfr_const_pi($pi, $ROUND);
        Math::MPFR::Rmpfr_mul_ui($t, $pi, 2, $ROUND);
        Math::MPFR::Rmpfr_log($t, $t, $ROUND);
        Math::MPFR::Rmpfr_mul_z($t, $t, $n, $ROUND);
        Math::MPFR::Rmpfr_sub($L, $L, $t, $ROUND);

        if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
            Math::MPFR::Rmpfr_zeta_ui($t, Math::GMPz::Rmpz_get_ui($n), $ROUND);
        }
        else {
            Math::MPFR::Rmpfr_set_z($t, $n, $ROUND);
            Math::MPFR::Rmpfr_zeta($t, $t, $ROUND);
        }
        Math::MPFR::Rmpfr_log($t, $t, $ROUND);
        Math::MPFR::Rmpfr_add($L, $L, $t, $ROUND);

        my $s = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_add_ui($s, $n, 1);
        Math::MPFR::Rmpfr_set_z($t, $s, $ROUND);
        Math::MPFR::Rmpfr_lngamma($t, $t, $ROUND);
        Math::MPFR::Rmpfr_add($L, $L, $t, $ROUND);

        if (Math::GMPz::Rmpz_divisible_2exp_p($n, 2)) {
            my $c = Math::MPC::Rmpc_init2(CORE::int($PREC));
            Math::MPC::Rmpc_set_fr_fr($c, $L, $pi, $ROUND);
            return bless \$c;
        }

        return bless \$L;
    }

    ref($x_ref) eq __PACKAGE__ or _valid(\$x_ref);

    # Explicit calculation of ln(B_n(x)) using Log-Sum-Exp
    my $n = _any2ui($$n_ref) // goto &nan;

    my $x_fr = _any2mpfr($$x_ref) // goto &nan;

    # Edge case: B_n(0) = B_n
    if (Math::MPFR::Rmpfr_zero_p($x_fr)) {
        return lnbernreal($n_ref);
    }

    my $log_x = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_abs($log_x, $x_fr, $ROUND);
    Math::MPFR::Rmpfr_log($log_x, $log_x, $ROUND);

    my $sgn_x = Math::MPFR::Rmpfr_sgn($x_fr);

    my $ln_n_fact = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set_ui($ln_n_fact, $n + 1, $ROUND);
    Math::MPFR::Rmpfr_lngamma($ln_n_fact, $ln_n_fact, $ROUND);    # ln(n!)

    my $ln_2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_log2($ln_2, $ROUND);

    my $ln_2pi = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_pi($ln_2pi, $ROUND);
    Math::MPFR::Rmpfr_mul_ui($ln_2pi, $ln_2pi, 2, $ROUND);
    Math::MPFR::Rmpfr_log($ln_2pi, $ln_2pi, $ROUND);              # ln(2π)

    my @L;                                                        # Stores ln|T_k|
    my @S;                                                        # Stores sign of T_k

    my $t1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    foreach my $k (0 .. $n) {
        next if ($k > 1 && $k % 2 != 0);

        my $L_k  = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $sign = 1;

        # Sign from x^(n-k)
        $sign = -$sign if ($sgn_x < 0 && ($n - $k) % 2 != 0);

        if ($k == 0) {

            # L_0 = n * ln|x|
            Math::MPFR::Rmpfr_mul_ui($L_k, $log_x, $n, $ROUND);
        }
        elsif ($k == 1) {

            # B_1 = -1/2 -> sign is negative
            $sign = -$sign;

            # L_1 = ln(n) - ln(2) + (n-1)*ln|x|
            Math::MPFR::Rmpfr_set_ui($t1, $n, $ROUND);
            Math::MPFR::Rmpfr_log($t1, $t1, $ROUND);
            Math::MPFR::Rmpfr_sub($L_k, $t1, $ln_2, $ROUND);

            if ($n - 1 > 0) {
                Math::MPFR::Rmpfr_mul_ui($t1, $log_x, $n - 1, $ROUND);
                Math::MPFR::Rmpfr_add($L_k, $L_k, $t1, $ROUND);
            }
        }
        else {
            # Sign from B_k (negative for k = 4, 8, 12, ...)
            $sign = -$sign if ($k % 4 == 0);

            # L_k = ln(n!) - ln((n-k)!) + ln(2) - k*ln(2π) + ln(zeta(k)) + (n-k)*ln|x|
            Math::MPFR::Rmpfr_set($L_k, $ln_n_fact, $ROUND);

            Math::MPFR::Rmpfr_set_ui($t1, $n - $k + 1, $ROUND);
            Math::MPFR::Rmpfr_lngamma($t1, $t1, $ROUND);
            Math::MPFR::Rmpfr_sub($L_k, $L_k, $t1, $ROUND);

            Math::MPFR::Rmpfr_add($L_k, $L_k, $ln_2, $ROUND);

            Math::MPFR::Rmpfr_mul_ui($t1, $ln_2pi, $k, $ROUND);
            Math::MPFR::Rmpfr_sub($L_k, $L_k, $t1, $ROUND);

            Math::MPFR::Rmpfr_zeta_ui($t1, $k, $ROUND);
            Math::MPFR::Rmpfr_log($t1, $t1, $ROUND);
            Math::MPFR::Rmpfr_add($L_k, $L_k, $t1, $ROUND);

            if ($n - $k > 0) {
                Math::MPFR::Rmpfr_mul_ui($t1, $log_x, $n - $k, $ROUND);
                Math::MPFR::Rmpfr_add($L_k, $L_k, $t1, $ROUND);
            }
        }

        push @L, $L_k;
        push @S, $sign;
    }

    # Log-Sum-Exp Accumulator
    my $L_max = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set($L_max, $L[0], $ROUND);
    for my $i (1 .. $#L) {
        Math::MPFR::Rmpfr_set($L_max, $L[$i], $ROUND) if (Math::MPFR::Rmpfr_cmp($L[$i], $L_max) > 0);
    }

    my $inner_sum = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_set_ui($inner_sum, 0, $ROUND);

    for my $i (0 .. $#L) {
        Math::MPFR::Rmpfr_sub($t1, $L[$i], $L_max, $ROUND);
        Math::MPFR::Rmpfr_exp($t1, $t1, $ROUND);
        if ($S[$i] < 0) {
            Math::MPFR::Rmpfr_sub($inner_sum, $inner_sum, $t1, $ROUND);
        }
        else {
            Math::MPFR::Rmpfr_add($inner_sum, $inner_sum, $t1, $ROUND);
        }
    }

    # Extremely rare exact cancellation across terms
    goto &ninf if Math::MPFR::Rmpfr_zero_p($inner_sum);

    my $sign_inner = Math::MPFR::Rmpfr_sgn($inner_sum);
    Math::MPFR::Rmpfr_abs($inner_sum, $inner_sum, $ROUND);
    Math::MPFR::Rmpfr_log($inner_sum, $inner_sum, $ROUND);

    my $result = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_add($result, $L_max, $inner_sum, $ROUND);

    # log(-Re(x)) = log(Re(x)) + i*π
    if ($sign_inner < 0) {
        my $pi = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_const_pi($pi, $ROUND);

        my $c = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr_fr($c, $result, $pi, $ROUND);
        return bless \$c;
    }

    return bless \$result;
}

*lnbern        = \&lnbernreal;
*bern_log      = \&lnbernreal;
*bernoulli_log = \&lnbernreal;

sub harmfrac {
    my ($n, $k) = @_;

    # Formula in terms of the Harmonic numbers, due to Conway and Guy (1996),
    # for computing the Harmonic numbers of the k-th order.
    if (defined($k)) {

        my $km1   = $k->dec;
        my $npkm1 = $n->add($km1);

        return $npkm1->binomial($km1)->mul($npkm1->harmfrac->sub($km1->harmfrac));
    }

    $n = _any2ui($$n) // goto &nan;
    $n || return ZERO();

    # Using harmfrac() from Math::Prime::Util::GMP
    my ($num, $den) = Math::Prime::Util::GMP::harmfrac($n);

    my $q = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_str($q, "$num/$den", 10);
    bless \$q;
}

*harm            = \&harmfrac;
*harmonic        = \&harmfrac;
*harmonic_number = \&harmfrac;

sub harmreal {
    my ($n, $k) = @_;

    # Formula in terms of the Harmonic numbers, due to Conway and Guy (1996),
    # for computing the Harmonic numbers of the k-th order.
    if (defined($k)) {

        my $km1   = $k->dec;
        my $npkm1 = $n->add($km1);

        return $npkm1->binomial($km1)->mul($npkm1->harmreal->sub($km1->harmreal));
    }

    $n = _any2mpfr($$n);

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_add_ui($r, $n, 1, $ROUND);
    Math::MPFR::Rmpfr_digamma($r, $r, $ROUND);

    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_euler($t, $ROUND);
    Math::MPFR::Rmpfr_add($r, $r, $t, $ROUND);

    bless \$r;
}

sub erf {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_erf($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

sub erfc {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_erfc($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

sub bessel_j {
    my ($x, $n) = @_;

    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); __numify__($$n) } : 0;

    if ($n < LONG_MIN or $n > ULONG_MAX) {
        return ZERO;
    }

    $x = _any2mpfr($$x);
    $n = CORE::int($n);

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    if ($n == 0) {
        Math::MPFR::Rmpfr_j0($r, $x, $ROUND);
    }
    elsif ($n == 1) {
        Math::MPFR::Rmpfr_j1($r, $x, $ROUND);
    }
    else {
        Math::MPFR::Rmpfr_jn($r, $n, $x, $ROUND);
    }

    bless \$r;
}

*BesselJ = \&bessel_j;

sub bessel_y {
    my ($x, $n) = @_;

    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); __numify__($$n) } : 0;

    if ($n < LONG_MIN or $n > ULONG_MAX) {
        if (__cmp__($$x, 0) < 0) {
            return nan();
        }
        return ($n < 0 ? inf() : ninf());
    }

    $x = _any2mpfr($$x);
    $n = CORE::int($n);

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    if ($n == 0) {
        Math::MPFR::Rmpfr_y0($r, $x, $ROUND);
    }
    elsif ($n == 1) {
        Math::MPFR::Rmpfr_y1($r, $x, $ROUND);
    }
    else {
        Math::MPFR::Rmpfr_yn($r, $n, $x, $ROUND);
    }

    bless \$r;
}

*BesselY = \&bessel_y;

sub eint {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_eint($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*ei = \&eint;
*Ei = \&eint;

sub ai {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_ai($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*airy = \&ai;
*Ai   = \&ai;

sub li {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_log($r, _any2mpfr($$x), $ROUND);
    Math::MPFR::Rmpfr_eint($r, $r, $ROUND);
    bless \$r;
}

*Li = \&li;

sub li2 {
    my ($x) = @_;
    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_li2($r, _any2mpfr($$x), $ROUND);
    bless \$r;
}

*Li2 = \&li2;

#
## Comparison and testing operations
#

sub __eq__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        return ($x == $y);
    }

  Scalar__Math_GMPz: {
        ($x, $y) = ($y, $x);
        goto Math_GMPz__Scalar;
    }

  Scalar__Math_GMPq: {
        ($x, $y) = ($y, $x);
        goto Math_GMPq__Scalar;
    }

  Scalar__Math_MPFR: {
        ($x, $y) = ($y, $x);
        goto Math_MPFR__Scalar;
    }

  Scalar__Math_MPC: {
        ($x, $y) = ($y, $x);
        goto Math_MPC__Scalar;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        return Math::MPFR::Rmpfr_equal_p($x, $y);
    }

  Math_MPFR__Math_GMPz: {
        return (Math::MPFR::Rmpfr_integer_p($x) and Math::MPFR::Rmpfr_cmp_z($x, $y) == 0);
    }

  Math_MPFR__Math_GMPq: {
        return (Math::MPFR::Rmpfr_number_p($x) and Math::MPFR::Rmpfr_cmp_q($x, $y) == 0);
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_MPFR__Scalar: {
        return (
                Math::MPFR::Rmpfr_integer_p($x)
                  and (
                       ($y || return !Math::MPFR::Rmpfr_sgn($x)) < 0
                       ? Math::MPFR::Rmpfr_cmp_si($x, $y)
                       : Math::MPFR::Rmpfr_cmp_ui($x, $y)
                  ) == 0
               );
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        return Math::GMPq::Rmpq_equal($x, $y);
    }

  Math_GMPq__Math_GMPz: {
        return (Math::GMPq::Rmpq_integer_p($x) and Math::GMPq::Rmpq_cmp_z($x, $y) == 0);
    }

  Math_GMPq__Math_MPFR: {
        return (Math::MPFR::Rmpfr_number_p($y) and Math::MPFR::Rmpfr_cmp_q($y, $x) == 0);
    }

  Math_GMPq__Math_MPC: {
        $x = _mpq2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_GMPq__Scalar: {
        return (
                Math::GMPq::Rmpq_integer_p($x)
                  and (
                       ($y || return !Math::GMPq::Rmpq_sgn($x)) < 0
                       ? Math::GMPq::Rmpq_cmp_si($x, $y, 1)
                       : Math::GMPq::Rmpq_cmp_ui($x, $y, 1)
                  ) == 0
               );
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        return (Math::GMPz::Rmpz_cmp($x, $y) == 0);
    }

  Math_GMPz__Math_GMPq: {
        return (Math::GMPq::Rmpq_integer_p($y) and Math::GMPq::Rmpq_cmp_z($y, $x) == 0);
    }

  Math_GMPz__Math_MPFR: {
        return (Math::MPFR::Rmpfr_integer_p($y) and Math::MPFR::Rmpfr_cmp_z($y, $x) == 0);
    }

  Math_GMPz__Math_MPC: {
        $x = _mpz2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_GMPz__Scalar: {
        return (
                (
                 ($y || return !Math::GMPz::Rmpz_sgn($x)) < 0
                 ? Math::GMPz::Rmpz_cmp_si($x, $y)
                 : Math::GMPz::Rmpz_cmp_ui($x, $y)
                ) == 0
               );
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $f1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $f2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($f1, $x);
        Math::MPC::RMPC_RE($f2, $y);

        Math::MPFR::Rmpfr_equal_p($f1, $f2) || return 0;

        Math::MPC::RMPC_IM($f1, $x);
        Math::MPC::RMPC_IM($f2, $y);

        return Math::MPFR::Rmpfr_equal_p($f1, $f2);
    }

  Math_MPC__Math_GMPz: {
        $y = _mpz2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_GMPq: {
        $y = _mpq2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Scalar: {
        my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_IM($f, $x);
        Math::MPFR::Rmpfr_zero_p($f) || return 0;
        Math::MPC::RMPC_RE($f, $x);
        $x = $f;
        goto Math_MPFR__Scalar;
    }
}

sub eq {
    my ($x, $y) = @_;

    ref($y) ne __PACKAGE__
      and return $y->eq($x);

    __eq__($$x, $$y) ? ($TRUE) : ($FALSE);
}

sub __ne__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        return ($x != $y);
    }

  Scalar__Math_GMPz: {
        ($x, $y) = ($y, $x);
        goto Math_GMPz__Scalar;
    }

  Scalar__Math_GMPq: {
        ($x, $y) = ($y, $x);
        goto Math_GMPq__Scalar;
    }

  Scalar__Math_MPFR: {
        ($x, $y) = ($y, $x);
        goto Math_MPFR__Scalar;
    }

  Scalar__Math_MPC: {
        ($x, $y) = ($y, $x);
        goto Math_MPC__Scalar;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        return !Math::MPFR::Rmpfr_equal_p($x, $y);
    }

  Math_MPFR__Math_GMPz: {
        return (!Math::MPFR::Rmpfr_integer_p($x) or Math::MPFR::Rmpfr_cmp_z($x, $y) != 0);
    }

  Math_MPFR__Math_GMPq: {
        return (!Math::MPFR::Rmpfr_number_p($x) or Math::MPFR::Rmpfr_cmp_q($x, $y) != 0);
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_MPFR__Scalar: {
        return (
                !Math::MPFR::Rmpfr_integer_p($x)
                  or (
                      ($y || return !!Math::MPFR::Rmpfr_sgn($x)) < 0
                      ? Math::MPFR::Rmpfr_cmp_si($x, $y)
                      : Math::MPFR::Rmpfr_cmp_ui($x, $y)
                  ) != 0
               );
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        return !Math::GMPq::Rmpq_equal($x, $y);
    }

  Math_GMPq__Math_GMPz: {
        return (!Math::GMPq::Rmpq_integer_p($x) or Math::GMPq::Rmpq_cmp_z($x, $y) != 0);
    }

  Math_GMPq__Math_MPFR: {
        return (!Math::MPFR::Rmpfr_number_p($y) or Math::MPFR::Rmpfr_cmp_q($y, $x) != 0);
    }

  Math_GMPq__Math_MPC: {
        $x = _mpq2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_GMPq__Scalar: {
        return (
                !Math::GMPq::Rmpq_integer_p($x)
                  or (
                      ($y || return !!Math::GMPq::Rmpq_sgn($x)) < 0
                      ? Math::GMPq::Rmpq_cmp_si($x, $y, 1)
                      : Math::GMPq::Rmpq_cmp_ui($x, $y, 1)
                  ) != 0
               );
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        return (Math::GMPz::Rmpz_cmp($x, $y) != 0);
    }

  Math_GMPz__Math_GMPq: {
        return (!Math::GMPq::Rmpq_integer_p($y) or Math::GMPq::Rmpq_cmp_z($y, $x) != 0);
    }

  Math_GMPz__Math_MPFR: {
        return (!Math::MPFR::Rmpfr_integer_p($y) or Math::MPFR::Rmpfr_cmp_z($y, $x) != 0);
    }

  Math_GMPz__Math_MPC: {
        $x = _mpz2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_GMPz__Scalar: {
        return (
                (
                 ($y || return !!Math::GMPz::Rmpz_sgn($x)) < 0
                 ? Math::GMPz::Rmpz_cmp_si($x, $y)
                 : Math::GMPz::Rmpz_cmp_ui($x, $y)
                ) != 0
               );
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {

        my $f1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $f2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($f1, $x);
        Math::MPC::RMPC_RE($f2, $y);

        Math::MPFR::Rmpfr_equal_p($f1, $f2) || return 1;

        Math::MPC::RMPC_IM($f1, $x);
        Math::MPC::RMPC_IM($f2, $y);

        return !Math::MPFR::Rmpfr_equal_p($f1, $f2);
    }

  Math_MPC__Math_GMPz: {
        $y = _mpz2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_GMPq: {
        $y = _mpq2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Scalar: {
        my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_IM($f, $x);
        Math::MPFR::Rmpfr_zero_p($f) || return 1;
        Math::MPC::RMPC_RE($f, $x);
        $x = $f;
        goto Math_MPFR__Scalar;
    }
}

sub ne {
    my ($x, $y) = @_;

    ref($y) ne __PACKAGE__
      and return $y->ne($x);

    __ne__($$x, $$y) ? ($TRUE) : ($FALSE);
}

sub __approx_cmp__ {
    my ($x, $y, $places, $equal) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (defined($places)) {
        ref($places) eq __PACKAGE__ or _valid(\$places);
        $places = _any2si($$places) // return undef;
    }
    else {
        $places = -((CORE::int($PREC) >> 2) - 1);
    }

    if (   ref($x) eq 'Math::MPFR'
        or ref($y) eq 'Math::MPFR'
        or ref($x) eq 'Math::MPC'
        or ref($y) eq 'Math::MPC') {
        $x = _any2mpfr_mpc($x);
        $y = _any2mpfr_mpc($y);
    }

    $x = __round__($x, $places);
    $y = __round__($y, $places);

    $equal ? __eq__($x, $y) : __cmp__($x, $y);
}

sub approx_cmp {
    my ($x, $y, $places) = @_;
    ((__approx_cmp__($x, $y, $places) // return undef) || return ZERO) > 0 ? ONE : MONE;
}

sub approx_lt {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places) // return undef) < 0 ? $TRUE : $FALSE;
}

sub approx_le {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places) // return undef) <= 0 ? $TRUE : $FALSE;
}

sub approx_gt {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places) // return undef) > 0 ? $TRUE : $FALSE;
}

sub approx_ge {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places) // return undef) >= 0 ? $TRUE : $FALSE;
}

sub approx_eq {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places, 1) // return undef) ? $TRUE : $FALSE;
}

sub approx_ne {
    my ($x, $y, $places) = @_;
    (__approx_cmp__($x, $y, $places, 1) // return undef) ? $FALSE : $TRUE;
}

sub __cmp__ {
    my ($x, $y) = @_;

    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #

  Scalar__Scalar: {
        return ($x <=> $y);
    }

  Math_GMPz__Scalar: {
        return (
                ($y || return Math::GMPz::Rmpz_sgn($x)) < 0
                ? Math::GMPz::Rmpz_cmp_si($x, $y)
                : Math::GMPz::Rmpz_cmp_ui($x, $y)
               );
    }

  Math_GMPq__Scalar: {
        return (
                ($y || return Math::GMPq::Rmpq_sgn($x)) < 0
                ? Math::GMPq::Rmpq_cmp_si($x, $y, 1)
                : Math::GMPq::Rmpq_cmp_ui($x, $y, 1)
               );
    }

  Math_MPFR__Scalar: {
        Math::MPFR::Rmpfr_nan_p($x) && return undef;
        return (
                ($y || return Math::MPFR::Rmpfr_sgn($x)) < 0
                ? Math::MPFR::Rmpfr_cmp_si($x, $y)
                : Math::MPFR::Rmpfr_cmp_ui($x, $y)
               );
    }

  Math_MPC__Scalar: {
        $y = _any2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Scalar__Math_GMPz: {
        my $r = __cmp__($y, $x) // return undef;
        return -$r;
    }

  Scalar__Math_GMPq: {
        my $r = __cmp__($y, $x) // return undef;
        return -$r;
    }

  Scalar__Math_MPFR: {
        my $r = __cmp__($y, $x) // return undef;
        return -$r;
    }

  Scalar__Math_MPC: {
        my $r = __cmp__($y, $x) // return undef;
        return -$r;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        if (   Math::MPFR::Rmpfr_nan_p($x)
            or Math::MPFR::Rmpfr_nan_p($y)) {
            return undef;
        }

        return Math::MPFR::Rmpfr_cmp($x, $y);
    }

  Math_MPFR__Math_GMPz: {
        Math::MPFR::Rmpfr_nan_p($x) && return undef;
        return Math::MPFR::Rmpfr_cmp_z($x, $y);
    }

  Math_MPFR__Math_GMPq: {
        Math::MPFR::Rmpfr_nan_p($x) && return undef;
        return Math::MPFR::Rmpfr_cmp_q($x, $y);
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {
        return Math::GMPq::Rmpq_cmp($x, $y);
    }

  Math_GMPq__Math_GMPz: {
        return Math::GMPq::Rmpq_cmp_z($x, $y);
    }

  Math_GMPq__Math_MPFR: {
        Math::MPFR::Rmpfr_nan_p($y) && return undef;
        return -(Math::MPFR::Rmpfr_cmp_q($y, $x));
    }

  Math_GMPq__Math_MPC: {
        $x = _mpq2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {
        return Math::GMPz::Rmpz_cmp($x, $y);
    }

  Math_GMPz__Math_GMPq: {
        return -(Math::GMPq::Rmpq_cmp_z($y, $x));
    }

  Math_GMPz__Math_MPFR: {
        Math::MPFR::Rmpfr_nan_p($y) && return undef;
        return -(Math::MPFR::Rmpfr_cmp_z($y, $x));
    }

  Math_GMPz__Math_MPC: {
        $x = _mpz2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($f, $x);
        Math::MPFR::Rmpfr_nan_p($f) && return undef;

        Math::MPC::RMPC_RE($f, $y);
        Math::MPFR::Rmpfr_nan_p($f) && return undef;

        Math::MPC::RMPC_IM($f, $x);
        Math::MPFR::Rmpfr_nan_p($f) && return undef;

        Math::MPC::RMPC_IM($f, $y);
        Math::MPFR::Rmpfr_nan_p($f) && return undef;

        my $si     = Math::MPC::Rmpc_cmp($x, $y);
        my $re_cmp = Math::MPC::RMPC_INEX_RE($si);
        $re_cmp == 0 or return $re_cmp;
        return Math::MPC::RMPC_INEX_IM($si);
    }

  Math_MPC__Math_GMPz: {
        $y = _mpz2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_GMPq: {
        $y = _mpq2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }
}

sub icmp {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    $x->to_i->cmp->($y->to_i);
}

*cmpint = \&icmp;

sub cmp {
    my ($x, $y) = @_;

    # Fast path for native scalar comparisons
    if (!ref($$x) && ref($y) eq __PACKAGE__ && !ref($$y)) {
        my $c = $$x <=> $$y;
        return !$c ? ZERO : ($c > 0) ? ONE : MONE;
    }

    ref($y) ne __PACKAGE__
      and return do {
        ($y->cmp($x) // return undef)->neg;
      };

    ref($y) eq __PACKAGE__ or _valid(\$y);
    my $cmp = __cmp__($$x, $$y) // return undef;
    !$cmp ? ZERO : ($cmp > 0) ? ONE : MONE;
}

sub acmp {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    my $cmp = __cmp__(__abs__($$x), __abs__($$y)) // return undef;
    !$cmp ? ZERO : ($cmp > 0) ? ONE : MONE;
}

sub gt {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ((__cmp__($$x, $$y) // return undef) > 0) ? ($TRUE) : ($FALSE);
}

sub ge {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ((__cmp__($$x, $$y) // return undef) >= 0) ? ($TRUE) : ($FALSE);
}

sub lt {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ((__cmp__($$x, $$y) // return undef) < 0) ? ($TRUE) : ($FALSE);
}

sub le {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ((__cmp__($$x, $$y) // return undef) <= 0) ? ($TRUE) : ($FALSE);
}

sub is_zero {
    my ($x) = @_;
    my $v = $$x;
    if (!ref($v)) {
        return $v == 0 ? $TRUE : $FALSE;
    }
    if (ref($v) eq 'Math::GMPz') {
        return Math::GMPz::Rmpz_sgn($v) == 0 ? $TRUE : $FALSE;
    }
    __eq__($v, 0) ? $TRUE : $FALSE;
}

sub is_one {
    my ($x) = @_;
    my $v = $$x;
    if (!ref($v)) {
        return $v == 1 ? $TRUE : $FALSE;
    }
    if (ref($v) eq 'Math::GMPz') {
        return Math::GMPz::Rmpz_cmp_ui($v, 1) == 0 ? $TRUE : $FALSE;
    }
    __eq__($v, 1) ? $TRUE : $FALSE;
}

sub is_mone {
    my ($x) = @_;
    my $v = $$x;
    if (!ref($v)) {
        return $v == -1 ? $TRUE : $FALSE;
    }
    if (ref($v) eq 'Math::GMPz') {
        return Math::GMPz::Rmpz_cmp_si($v, -1) == 0 ? $TRUE : $FALSE;
    }
    __eq__($v, -1) ? ($TRUE) : ($FALSE);
}

sub is_positive {
    my ($x) = @_;
    my $v = $$x;
    if (!ref($v)) {
        return $v > 0 ? $TRUE : $FALSE;
    }
    if (ref($v) eq 'Math::GMPz') {
        return Math::GMPz::Rmpz_sgn($v) > 0 ? $TRUE : $FALSE;
    }
    ((__cmp__($v, 0) // return undef) > 0) ? $TRUE : $FALSE;
}

*is_pos = \&is_positive;

sub is_negative {
    my ($x) = @_;
    my $v = $$x;
    if (!ref($v)) {
        return $v < 0 ? $TRUE : $FALSE;
    }
    if (ref($v) eq 'Math::GMPz') {
        return Math::GMPz::Rmpz_sgn($v) < 0 ? $TRUE : $FALSE;
    }
    ((__cmp__($v, 0) // return undef) < 0) ? ($TRUE) : ($FALSE);
}

*is_neg = \&is_negative;

sub __sgn__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        return 1  if ($x > 0);
        return -1 if ($x < 0);
        return 0;
    }

  Math_MPFR: {
        return Math::MPFR::Rmpfr_sgn($x);
    }

  Math_GMPq: {
        return Math::GMPq::Rmpq_sgn($x);
    }

  Math_GMPz: {
        return Math::GMPz::Rmpz_sgn($x);
    }

  Math_MPC: {
        my $abs = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::Rmpc_abs($abs, $x, $ROUND);

        if (Math::MPFR::Rmpfr_zero_p($abs)) {    # it's zero
            return 0;
        }

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_div_fr($r, $x, $abs, $ROUND);
        return $r;
    }
}

sub sign {
    my ($x) = @_;
    my $r = __sgn__($$x);
    if (ref($r)) {
        bless \$r;
    }
    else {
        ($r < 0) ? MONE : ($r > 0) ? ONE : ZERO;
    }
}

*sgn = \&sign;

sub popcount {
    my ($x) = @_;
    my $z = _any2mpz($$x) // return undef;

    if (Math::GMPz::Rmpz_sgn($z) < 0) {
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_neg($t, $z);
        $z = $t;
    }

    _set_int(Math::GMPz::Rmpz_popcount($z));
}

*hammingweight = \&popcount;

# Hamming distance
sub hamdist {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // return undef;
    $k = _any2mpz($$k) // return undef;

    _set_int(Math::GMPz::Rmpz_hamdist($n, $k));
}

sub __is_int__ {
    my ($x) = @_;

    ref($x) || return 1;
    ref($x) eq 'Math::GMPz' && return 1;
    ref($x) eq 'Math::GMPq' && return Math::GMPq::Rmpq_integer_p($x);
    ref($x) eq 'Math::MPFR' && return Math::MPFR::Rmpfr_integer_p($x);

    (@_) = _any2mpfr($x);
    goto __SUB__;
}

sub is_int {
    my ($x) = @_;
    __is_int__($$x) ? ($TRUE) : ($FALSE);
}

sub __is_rat__ {
    my ($x) = @_;
    !ref($x) or ref($x) eq 'Math::GMPz' or ref($x) eq 'Math::GMPq';
}

sub is_rat {
    my ($x) = @_;
    __is_rat__($$x) ? ($TRUE) : ($FALSE);
}

sub __is_float__ {
    my ($x) = @_;
    ref($x) eq 'Math::MPFR' or ref($x) eq 'Math::MPC';
}

sub is_float {
    my ($x) = @_;
    __is_float__($$x) ? ($TRUE) : ($FALSE);
}

sub __is_real__ {
    my ($x) = @_;

    ref($x) || return 1;
    ref($x) eq 'Math::GMPz' && return 1;
    ref($x) eq 'Math::GMPq' && return 1;
    ref($x) eq 'Math::MPFR' && return Math::MPFR::Rmpfr_number_p($x);

    (@_) = _any2mpfr($x);
    goto __SUB__;
}

sub is_real {
    my ($x) = @_;
    __is_real__($$x) ? ($TRUE) : ($FALSE);
}

sub __is_imag__ {
    my ($x) = @_;

    ref($x) eq 'Math::MPC' or return 0;

    my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPC::RMPC_RE($f, $x);
    Math::MPFR::Rmpfr_zero_p($f) || return 0;    # is complex
    Math::MPC::RMPC_IM($f, $x);
    !Math::MPFR::Rmpfr_zero_p($f);
}

sub is_imag {
    my ($x) = @_;
    __is_imag__($$x) ? ($TRUE) : ($FALSE);
}

sub __is_complex__ {
    my ($x) = @_;

    ref($x) eq 'Math::MPC' or return 0;

    my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPC::RMPC_IM($f, $x);
    Math::MPFR::Rmpfr_zero_p($f) && return 0;    # is real
    Math::MPC::RMPC_RE($f, $x);
    !Math::MPFR::Rmpfr_zero_p($f);
}

sub is_complex {
    my ($x) = @_;
    __is_complex__($$x) ? ($TRUE) : ($FALSE);
}

sub is_between {
    my ($x, $min, $max) = @_;
    ref($min) eq __PACKAGE__ or _valid(\$min);
    ref($max) eq __PACKAGE__ or _valid(\$max);
    (__cmp__($$x, $$min) >= 0 and __cmp__($$x, $$max) <= 0) ? ($TRUE) : ($FALSE);
}

sub is_even {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return (($x % 2 == 0) ? ($TRUE) : ($FALSE));
    }

    (__is_int__($x) && Math::GMPz::Rmpz_even_p(_any2mpz($x) // (return $FALSE))) ? ($TRUE) : ($FALSE);
}

sub is_odd {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return (($x % 2 == 1) ? ($TRUE) : ($FALSE));
    }

    (__is_int__($x) && Math::GMPz::Rmpz_odd_p(_any2mpz($x) // (return $FALSE))) ? ($TRUE) : ($FALSE);
}

sub is_congruent {
    my ($n, $k, $m) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = $$n;
    $k = $$k;
    $m = $$m;

    if (FAST_MODE and !ref($n) and !ref($k) and !ref($m)) {
        $m || return $FALSE;
        return ((($k % $m) == ($n % $m)) ? ($TRUE) : ($FALSE));
    }

    if (ref($n) eq 'Math::GMPz' and ref($k) eq 'Math::GMPz' and ref($m) eq 'Math::GMPz') {
        Math::GMPz::Rmpz_sgn($m) || return $FALSE;
        return (Math::GMPz::Rmpz_congruent_p($n, $k, $m) ? ($TRUE) : ($FALSE));
    }

    __eq__(__mod__($n, $m), __mod__($k, $m)) ? ($TRUE) : ($FALSE);
}

sub is_div {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        $y == 0 and return $FALSE;
        return (($x % $y == 0) ? ($TRUE) : ($FALSE));
    }

    if (ref($x) eq 'Math::GMPz') {

        if (FAST_MODE and !ref($y)) {
            $y == 0 and return $FALSE;
            return (Math::GMPz::Rmpz_divisible_ui_p($x, CORE::abs($y)) ? ($TRUE) : ($FALSE));
        }

        if (ref($y) eq 'Math::GMPz') {
            return ((Math::GMPz::Rmpz_divisible_p($x, $y) && Math::GMPz::Rmpz_sgn($y)) ? ($TRUE) : ($FALSE));
        }
    }

    __eq__(__mod__($x, $y), 0) ? ($TRUE) : ($FALSE);
}

*is_divisible    = \&is_div;
*is_divisible_by = \&is_div;

sub divides {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x)) {
        $x == 0 and return $FALSE;
        if (!ref($y)) {
            return (($y % $x == 0) ? ($TRUE) : ($FALSE));
        }
        elsif (ref($y) eq 'Math::GMPz') {
            return (Math::GMPz::Rmpz_divisible_ui_p($y, CORE::abs($x)) ? ($TRUE) : ($FALSE));
        }
    }

    if (ref($x) eq 'Math::GMPz' and ref($y) eq 'Math::GMPz') {
        return ((Math::GMPz::Rmpz_divisible_p($y, $x) && Math::GMPz::Rmpz_sgn($x)) ? ($TRUE) : ($FALSE));
    }

    __eq__(__mod__($y, $x), 0) ? ($TRUE) : ($FALSE);
}

sub __is_inf__ {
    my ($x) = @_;

    ref($x) || return 0;
    ref($x) eq 'Math::GMPz' && return 0;
    ref($x) eq 'Math::GMPq' && return 0;
    ref($x) eq 'Math::MPFR' && return (Math::MPFR::Rmpfr_inf_p($x) and Math::MPFR::Rmpfr_sgn($x) > 0);

    (@_) = _any2mpfr($x);
    goto __SUB__;
}

sub is_inf {
    my ($x) = @_;
    __is_inf__($$x) ? $TRUE : $FALSE;
}

sub __is_ninf__ {
    my ($x) = @_;

    ref($x) || return 0;
    ref($x) eq 'Math::GMPz' && return 0;
    ref($x) eq 'Math::GMPq' && return 0;
    ref($x) eq 'Math::MPFR' && return (Math::MPFR::Rmpfr_inf_p($x) and Math::MPFR::Rmpfr_sgn($x) < 0);

    (@_) = _any2mpfr($x);
    goto __SUB__;
}

sub is_ninf {
    my ($x) = @_;
    __is_ninf__($$x) ? $TRUE : $FALSE;
}

sub is_nan {
    my ($x) = @_;

    $x = $$x;

    ref($x) || return $FALSE;
    ref($x) eq 'Math::GMPz' && return $FALSE;
    ref($x) eq 'Math::GMPq' && return $FALSE;
    ref($x) eq 'Math::MPFR'
      && return (Math::MPFR::Rmpfr_nan_p($x) ? $TRUE : $FALSE);

    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPC::RMPC_RE($t, $x);
    Math::MPFR::Rmpfr_nan_p($t) && return $TRUE;

    Math::MPC::RMPC_IM($t, $x);
    Math::MPFR::Rmpfr_nan_p($t) && return $TRUE;

    $FALSE;
}

sub sum {
    my (@vals) = @_;

    @vals || return ZERO;

    my @extra;
    my @numbers;

    foreach my $value (@vals) {
        if (ref($value) eq __PACKAGE__) {
            push @numbers, $$value;
        }
        elsif (exists $NUMBER_CLASSES{ref($value)}) {
            push @extra, $value;
        }
        else {
            _valid(\$value);
            push @numbers, $$value;
        }
    }

    my @nonmpz;
    my ($native_sum, $new_sum, $sum) = (0, 0, undef);

    foreach my $n (@numbers) {
        if (FAST_MODE and !ref($n)) {
            $new_sum = $native_sum + $n;

            if ($new_sum < ULONG_MAX and $new_sum > LONG_MIN) {
                $native_sum = $new_sum;
            }
            else {
                $sum //= Math::GMPz::Rmpz_init_set_ui(0);
                ($native_sum < 0)
                  ? Math::GMPz::Rmpz_sub_ui($sum, $sum, -$native_sum)
                  : Math::GMPz::Rmpz_add_ui($sum, $sum, $native_sum);
                $native_sum = $n;    # reset the sum
            }
        }
        elsif (ref($n) eq 'Math::GMPz') {
            $sum //= Math::GMPz::Rmpz_init_set_ui(0);
            Math::GMPz::Rmpz_add($sum, $sum, $n);
        }
        else {
            push @nonmpz, $n;
        }
    }

    if (!defined($sum)) {
        $sum = $native_sum;
    }
    elsif ($native_sum != 0) {

        ($native_sum < 0)
          ? Math::GMPz::Rmpz_sub_ui($sum, $sum, -$native_sum)
          : Math::GMPz::Rmpz_add_ui($sum, $sum, $native_sum);

        if (Math::GMPz::Rmpz_fits_slong_p($sum)) {
            $sum = Math::GMPz::Rmpz_get_si($sum);
        }
    }

    if (@nonmpz) {
        $sum = __add__($sum, _binsplit(\@nonmpz, \&__add__));
    }

    my $r = bless \$sum;

    if (@extra) {
        $r = $r->add(_binsplit(\@extra, sub { $_[0]->add($_[1]) }));
    }

    $r;
}

*Σ      = \&sum;
*vecsum = \&sum;

sub prod {
    my (@vals) = @_;

    @vals || return ONE;

    my @numbers;
    my @unknown;

    foreach my $value (@vals) {
        if (ref($value) eq __PACKAGE__) {
            push @numbers, $$value;
        }
        elsif (exists $NUMBER_CLASSES{ref($value)}) {
            push @unknown, $value;
        }
        else {
            _valid(\$value);
            push @numbers, $$value;
        }
    }

    my $r = (@numbers ? (bless \_binsplit(\@numbers, \&__mul__)) : ONE);

    if (@unknown) {
        $r = $r->mul(_binsplit(\@unknown, sub { $_[0]->mul($_[1]) }));
    }

    return $r;
}

*Π       = \&prod;
*vecprod = \&prod;

sub max {
    my (@vals) = @_;
    _valid(\(@vals));

    my $max   = shift(@vals) // return undef;
    my $v_max = $$max;

    foreach my $curr (@vals) {
        my $v_curr = $$curr;

        # Native fast-path
        if (!ref($v_curr) && !ref($v_max)) {
            if ($v_curr > $v_max) {
                $max   = $curr;
                $v_max = $v_curr;
            }
        }
        elsif ((__cmp__($v_curr, $v_max) // return undef) > 0) {
            $max   = $curr;
            $v_max = $v_curr;
        }
    }

    return $max;
}

*vecmax = \&max;

sub min {
    my (@vals) = @_;
    _valid(\(@vals));

    my $min   = shift(@vals) // return undef;
    my $v_min = $$min;

    foreach my $curr (@vals) {
        my $v_curr = $$curr;

        # Native fast-path
        if (!ref($v_curr) && !ref($v_min)) {
            if ($v_curr < $v_min) {
                $min   = $curr;
                $v_min = $v_curr;
            }
        }
        elsif ((__cmp__($v_curr, $v_min) // return undef) < 0) {
            $min   = $curr;
            $v_min = $v_curr;
        }
    }

    return $min;
}

*vecmin = \&min;

sub as_int {
    my ($x, $y) = @_;

    my $base = 10;
    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $base = _any2ui($$y) // 0;
        if ($base < 2 or $base > 62) {
            die "[ERROR] Number.as_int(): base must be between 2 and 62, got $y";
        }
    }

    Sidef::Types::String::String->new(Math::GMPz::Rmpz_get_str((_any2mpz($$x) // return undef), $base));
}

sub __base__ {
    my ($x, $base) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_GMPz: {
        return Math::GMPz::Rmpz_get_str($x, $base);
    }

  Math_GMPq: {
        return Math::GMPq::Rmpq_get_str($x, $base);
    }

  Math_MPFR: {
        return Math::MPFR::Rmpfr_get_str($x, $base, 0, $ROUND);
    }

  Math_MPC: {
        my $fr = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPC::RMPC_RE($fr, $x);
        my $real = __base__($fr, $base);
        Math::MPC::RMPC_IM($fr, $x);
        return $real if Math::MPFR::Rmpfr_zero_p($fr);
        my $imag = __base__($fr, $base);
        return "($real $imag)";
    }
}

sub base {
    my ($x, $y) = @_;

    my $base = 10;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $base = _any2ui($$y) // 0;
        if ($base < 2 or $base > 62) {
            die "[ERROR] Number.base(): base must be between 2 and 62, got $y";
        }
    }

    $x = $$x;

    Sidef::Types::String::String->new(__base__($x, $base));
}

*in_base = \&base;

sub as_rat {
    my ($x, $y) = @_;

    my $base = 10;
    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $base = _any2ui($$y) // 0;
        if ($base < 2 or $base > 62) {
            die "[ERROR] base must be between 2 and 62, got $y";
        }
    }

    my $str =
      ref($$x) eq 'Math::GMPz'
      ? Math::GMPz::Rmpz_get_str($$x, $base)
      : Math::GMPq::Rmpq_get_str((_any2mpq($$x) // return undef), $base);

    Sidef::Types::String::String->new($str);
}

sub as_frac {
    my ($x, $y) = @_;

    my $base = 10;
    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $base = _any2ui($$y) // 0;
        if ($base < 2 or $base > 62) {
            die "as_frac(): base must be between 2 and 62, got $y";
        }
    }

    my $str =
      ref($$x) eq 'Math::GMPz'
      ? Math::GMPz::Rmpz_get_str($$x, $base)
      : Math::GMPq::Rmpq_get_str((_any2mpq($$x) // return undef), $base);

    $str .= '/1' if (index($str, '/') == -1);

    Sidef::Types::String::String->new($str);
}

sub as_cfrac {
    my ($x, $n) = @_;

    my $p = CORE::int($PREC) >> 1;

    $x = $$x;
    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _any2ui($$n) // 0 } : ($p >> 1);

    goto($DISPATCH_TAG{ref($x)});

  Math_GMPq: {
        my @cfrac;
        my $q = Math::GMPq::Rmpq_init();

        Math::GMPq::Rmpq_set($q, $x);

        for (1 .. $n) {
            my $z = __floor__($q);
            push @cfrac, bless \$z;
            Math::GMPq::Rmpq_sub_z($q, $q, _any2mpz($z));
            Math::GMPq::Rmpq_sgn($q) || last;
            Math::GMPq::Rmpq_inv($q, $q);
        }

        return _array(\@cfrac);
    }

  Math_MPFR: {
        my @cfrac;
        my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPFR::Rmpfr_set($f, $x, $ROUND);

        for (1 .. $n) {
            my $t = __floor__($f);
            push @cfrac, bless \(_any2mpz($t) // $t);

            Math::MPFR::Rmpfr_eq($f, $t, $p) && last;
            Math::MPFR::Rmpfr_sub($f, $f, $t, $ROUND);
            Math::MPFR::Rmpfr_ui_div($f, 1, $f, $ROUND);
        }

        return _array(\@cfrac);
    }

  Math_MPC: {
        my @cfrac;
        my $c = Math::MPC::Rmpc_init2(CORE::int($PREC));

        my $real_1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $real_2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        my $imag_1 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag_2 = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        my $one = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_ui($one, 1, $ROUND);

        Math::MPC::Rmpc_set($c, $x, $ROUND);

        for (1 .. $n) {
            my $t = __round__($c, 0);
            push @cfrac, bless \(_any2mpz($t) // $t);

            Math::MPC::Rmpc_real($real_1, $c, $ROUND);
            Math::MPC::Rmpc_imag($imag_1, $c, $ROUND);

            if (ref($t) eq 'Math::MPFR') {
                Math::MPFR::Rmpfr_neg($t, $t, $ROUND);
                Math::MPC::Rmpc_add_fr($c, $c, $t, $ROUND);
                Math::MPFR::Rmpfr_neg($t, $t, $ROUND);

                Math::MPFR::Rmpfr_set($real_2, $t, $ROUND);
                Math::MPFR::Rmpfr_set_ui($imag_2, 0, $ROUND);
            }
            else {
                Math::MPC::Rmpc_sub($c, $c, $t, $ROUND);

                Math::MPC::Rmpc_real($real_2, $t, $ROUND);
                Math::MPC::Rmpc_imag($imag_2, $t, $ROUND);
            }

#<<<
               Math::MPFR::Rmpfr_eq($real_1, $real_2, $p)
            && Math::MPFR::Rmpfr_eq($imag_1, $imag_2, $p)
            && last;
#>>>

            Math::MPC::Rmpc_div($c, $one, $c, $ROUND);
        }

        return _array(\@cfrac);
    }

  Math_GMPz: {
        return _array([bless \$x]);
    }

  Scalar: {
        return _array([bless \$x]);
    }
}

*cfrac = \&as_cfrac;

sub as_float {
    my ($x, $prec) = @_;

    if (defined($prec)) {
        ref($prec) eq __PACKAGE__ or _valid(\$prec);
        $prec = (_any2ui($$prec) // 0) << 2;

        state $min_prec = Math::MPFR::RMPFR_PREC_MIN();
        state $max_prec = Math::MPFR::RMPFR_PREC_MAX();

        if ($prec < $min_prec or $prec > $max_prec) {
            die "as_float(): precision must be between $min_prec and $max_prec, got ", $prec >> 2;
        }
    }
    else {
        $prec = CORE::int($PREC);
    }

    local $PREC = $prec;
    Sidef::Types::String::String->new(__stringify__(_any2mpfr_mpc($$x)));
}

*as_dec = \&as_float;

# Utility: Polynomial multiplication modulo P(x) (Fiduccia's algorithm core)
sub _poly_mul_mod {
    my ($A, $B, $ker, $k, $mod) = @_;

    # Pre-allocate result array to avoid dynamic resizing
    my @C = map { Math::GMPz::Rmpz_init_set_ui(0) } (0 .. 2 * $k - 2);

    # Polynomial multiplication (schoolbook)
    for my $i (0 .. $k - 1) {
        my $ai = $A->[$i] // next;
        Math::GMPz::Rmpz_sgn($ai) || next;

        for my $j (0 .. $k - 1) {
            my $bj = $B->[$j] // next;
            Math::GMPz::Rmpz_sgn($bj) || next;

            my $idx = $i + $j;
            Math::GMPz::Rmpz_addmul($C[$idx], $ai, $bj);
        }
    }

    # Apply mod if provided
    if (defined $mod) {
        for my $c (@C) {
            Math::GMPz::Rmpz_mod($c, $c, $mod);
        }
    }

    # Reduce modulo characteristic polynomial P(x)
    for (my $i = $#C ; $i >= $k ; $i--) {
        my $c = $C[$i];
        Math::GMPz::Rmpz_sgn($c) || next;

        # Multiply and subtract: C -= c * ker * x^{i-k}
        for my $j (1 .. $k) {
            my $idx = $i - $j;
            Math::GMPz::Rmpz_addmul($C[$idx], $c, $ker->[$j - 1]);
        }

        # Apply mod if provided (only to updated indices)
        if (defined $mod) {
            for my $j (1 .. $k) {
                my $idx = $i - $j;
                Math::GMPz::Rmpz_mod($C[$idx], $C[$idx], $mod);
            }
        }

        # Zero out the reduced coefficient
        Math::GMPz::Rmpz_set_ui($C[$i], 0);
    }

    # Truncate to degree k-1
    $#C = $k - 1;

    return \@C;
}

# Utility: Polynomial exponentiation modulo P(x)
sub _poly_pow_mod {
    my ($n, $ker, $k, $mod) = @_;

    # Pre-allocate result and base
    my @res = map { Math::GMPz::Rmpz_init_set_ui(0) } (0 .. $k - 1);
    $res[0] //= Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_set_ui($res[0], 1);    # Start with 1

    my @base = map { Math::GMPz::Rmpz_init_set_ui(0) } (0 .. $k - 1);
    $base[1] //= Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_set_ui($base[1], 1);    # x^1

    # Edge case: degree 1 reduction (P(x) = a0 + a1*x)
    if ($k == 1) {
        Math::GMPz::Rmpz_set($base[0], $ker->[0]);
        Math::GMPz::Rmpz_mod($base[0], $base[0], $mod) if defined $mod;
    }

    my $exp = Math::GMPz::Rmpz_init_set($n);

    while (Math::GMPz::Rmpz_sgn($exp) > 0) {
        if (Math::GMPz::Rmpz_odd_p($exp)) {
            @res = @{_poly_mul_mod(\@res, \@base, $ker, $k, $mod)};
        }

        Math::GMPz::Rmpz_fdiv_q_2exp($exp, $exp, 1);

        if (Math::GMPz::Rmpz_sgn($exp) > 0) {
            @base = @{_poly_mul_mod(\@base, \@base, $ker, $k, $mod)};
        }
    }

    return \@res;
}

sub _linear_recurrence_mpz {
    my ($ker, $init, $n_min, $n_max) = @_;

    my $want_array = 1;
    if (!defined($n_max)) {
        $n_max      = $n_min;
        $want_array = 0;
    }

    my $k = scalar(@$ker);
    return $want_array ? _array() : ZERO if $k == 0;

    # Convert kernel and initial terms to GMPz
    my @gmp_ker    = map { _any2mpz($$_) // goto &nan } @$ker;
    my @init_terms = map { _any2mpz($$_) // goto &nan } @$init[0 .. ($#$init > $#$ker ? $#$ker : $#$init)];

    # Pad initial terms with zeros if needed
    while (@init_terms < $k) {
        push @init_terms, $ZERO;
    }

    my $n = _any2mpz($$n_min) // goto &nan;

    # Compute x^n mod P(x)
    my $res = _poly_pow_mod($n, \@gmp_ker, $k, undef);

    # Fast-path: single value
    if (!$want_array) {
        my $val = Math::GMPz::Rmpz_init_set_ui(0);
        for my $i (0 .. $k - 1) {
            next unless defined $res->[$i];
            Math::GMPz::Rmpz_addmul($val, $res->[$i], $init_terms[$i]);
        }
        return bless \$val;
    }

    # Compute the range
    my $count = Math::GMPz::Rmpz_init_set(_any2mpz($$n_max) // return _array());
    Math::GMPz::Rmpz_sub($count, $count, $n);
    return _array() if Math::GMPz::Rmpz_sgn($count) < 0;

    my $total_terms = Math::GMPz::Rmpz_get_ui($count) + 1;
    my @result_seq;
    my @window;

    # Compute initial window: [x^{n_min} * init, x^{n_min+1} * init, ..., x^{n_min+k-1} * init]
    for my $step (0 .. $k - 1) {
        my $val = Math::GMPz::Rmpz_init_set_ui(0);
        for my $i (0 .. $k - 1) {
            Math::GMPz::Rmpz_addmul($val, $res->[$i], $init_terms[$i]) if Math::GMPz::Rmpz_sgn($res->[$i]);
        }
        push @window, $val;

        # Multiply R(x) by x mod P(x) for sequential state steps
        if ($step < $k - 1) {
            my $top     = $res->[$k - 1];
            my $sgn_top = Math::GMPz::Rmpz_sgn($top);
            for (my $i = $k - 1 ; $i >= 1 ; $i--) {
                $res->[$i] = Math::GMPz::Rmpz_init_set($res->[$i - 1]);
                Math::GMPz::Rmpz_addmul($res->[$i], $top, $gmp_ker[$k - 1 - $i]) if $sgn_top;
            }
            Math::GMPz::Rmpz_set_ui($res->[0], 0);
            Math::GMPz::Rmpz_addmul($res->[0], $top, $gmp_ker[$k - 1]) if $sgn_top;
        }
    }

    # LFSR: generate terms from n_min to n_max
    for my $i (0 .. $total_terms - 1) {
        if ($i < $k) {
            my $val = $window[$i];
            push @result_seq, bless \$val;
        }
        else {
            # Compute next term: sum_{j=0}^{k-1} ker[j] * window[k-1-j]
            my $next_val = Math::GMPz::Rmpz_init_set_ui(0);
            for my $j (0 .. $k - 1) {
                Math::GMPz::Rmpz_addmul($next_val, $gmp_ker[$j], $window[$k - 1 - $j]);
            }

            shift @window;
            push @window,     $next_val;
            push @result_seq, bless \$next_val;
        }
    }

    return _array(\@result_seq);
}

sub _linear_recurrence_mod_mpz {
    my ($ker, $init, $n, $m) = @_;

    my $k = scalar(@$ker);
    return Sidef::Types::Number::Number::ZERO if $k == 0;

    my $gmp_m = _any2mpz($$m) // goto &nan;
    Math::GMPz::Rmpz_sgn($gmp_m) || goto &nan;

    # Pre-allocate kernel and initial terms with mod applied
    my @gmp_ker = map {
        my $z = _any2mpz($$_) // goto &nan;
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mod($t, $z, $gmp_m);
        $t;
    } @$ker;

    my @init_terms = map {
        my $z = _any2mpz($$_) // goto &nan;
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mod($t, $z, $gmp_m);
        $t;
    } @$init[0 .. ($#$init > $#$ker ? $#$ker : $#$init)];

    # Pad with zeros
    while (@init_terms < $k) {
        push @init_terms, $ZERO;
    }

    my $gmp_n = _any2mpz($$n) // goto &nan;
    my $res   = _poly_pow_mod($gmp_n, \@gmp_ker, $k, $gmp_m);

    # Compute dot product: res · init_terms
    my $val = Math::GMPz::Rmpz_init_set_ui(0);
    for my $i (0 .. $k - 1) {
        next unless defined $res->[$i];
        Math::GMPz::Rmpz_addmul($val, $res->[$i], $init_terms[$i]);
    }
    Math::GMPz::Rmpz_mod($val, $val, $gmp_m);

    return bless \$val;
}

sub _solve_rec_seq {    # Berlekamp-Massey algorithm
    my (@seq) = @_;

    # Reference:
    #   https://youtube.com/watch?v=NO1_-qptr6c

    _valid(\(@seq));

    my $n = scalar(@seq);
    return _array() if ($n == 0);

    my $from_mpq = sub {
        my ($q) = @_;

        if (Math::GMPq::Rmpq_integer_p($q)) {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPq::Rmpq_get_num($z, $q);
            return bless \$z;
        }

        return bless \$q;
    };

    my @s = map { _any2mpq($$_) // return _array() } @seq;

    my @C  = (Math::GMPq->new(1));    # connection polynomial coefficients
    my @B  = (Math::GMPq->new(1));    # B from the last length-increase step
    my $L  = 0;                       # LFSR length
    my $m  = 1;                       # steps since last length increase
    my $b0 = Math::GMPq->new(1);      # discrepancy at last length increase

    my $tmp   = Math::GMPq::Rmpq_init();
    my $coeff = Math::GMPq::Rmpq_init();

    for my $i (0 .. $n - 1) {

        my $d = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set($d, $s[$i]);

        for my $j (1 .. $L) {
            Math::GMPq::Rmpq_mul($tmp, $C[$j], $s[$i - $j]);
            Math::GMPq::Rmpq_add($d, $d, $tmp);
        }

        # Zero discrepancy — C already explains this term.
        if (Math::GMPq::Rmpq_sgn($d) == 0) {
            ++$m;
            next;
        }

        # δ = d / b
        Math::GMPq::Rmpq_div($coeff, $d, $b0);

        my @T = map {
            my $q = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_set($q, $_);
            $q
        } @C;

        # Ensure C has enough slots: highest index touched = ($#B + $m).
        my $need = @B + $m;
        push @C, Math::GMPq::Rmpq_init() while (scalar(@C) < $need);

        # C(x) ← C(x) − δ · xᵐ · B(x)
        for my $j (0 .. $#B) {
            Math::GMPq::Rmpq_mul($tmp, $coeff, $B[$j]);
            Math::GMPq::Rmpq_sub($C[$j + $m], $C[$j + $m], $tmp);
        }

        if (2 * $L <= $i) {
            $L = $i + 1 - $L;
            @B = @T;
            Math::GMPq::Rmpq_set($b0, $d);
            $m = 1;
        }
        else {
            ++$m;
        }
    }

    my @result;
    for my $k (1 .. $L) {
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_neg($q, $C[$k]);
        push @result, $from_mpq->($q);
    }

    _array(\@result);
}

sub _solve_seq {    # Newton's forward difference formula
    my ($offset, @sequence) = @_;

    ref($offset) eq __PACKAGE__ or _valid(\$offset);
    _valid(\(@sequence));

    my $N = scalar(@sequence);

    if ($N == 0) {
        return Sidef::Types::Number::Polynomial->new();
    }

    # Extract sequence into raw Math::GMPq objects for speed
    my @S = map { _any2mpq($$_) // return Sidef::Types::Number::Polynomial->new() } @sequence;
    my @c;

    for my $k (0 .. $N - 1) {

        my $all_zero = 1;
        for my $i (0 .. $N - 1 - $k) {
            if (Math::GMPq::Rmpq_sgn($S[$i]) != 0) {
                $all_zero = 0;
                last;
            }
        }

        last if $all_zero;

        # Capture the leading delta coefficient for the current degree
        $c[$k] = Math::GMPq->new($S[0]);

        # Generate next difference tier within the same buffer
        for my $i (0 .. $N - 2 - $k) {
            Math::GMPq::Rmpq_sub($S[$i], $S[$i + 1], $S[$i]);
        }
    }

    # If all terms were zero, return an empty polynomial
    return Sidef::Types::Number::Polynomial->new() if !@c;

    my @master_poly;
    my @P          = (Math::GMPq->new(1));
    my $fact       = Math::GMPz::Rmpz_init_set_ui(1);
    my $offset_mpz = defined($offset) ? _any2mpz($$offset) : Math::GMPz::Rmpz_init_set_ui(0);

    # Pre-allocate mathematical scratchpads
    my $f_k   = Math::GMPq::Rmpq_init();
    my $term  = Math::GMPq::Rmpq_init();
    my $alpha = Math::GMPq::Rmpq_init();
    my $tmp   = Math::GMPq::Rmpq_init();

    for my $k (0 .. $#c) {
        my $ck = $c[$k];

        if (Math::GMPq::Rmpq_sgn($ck) != 0) {

            # f_k = c_k / k!
            Math::GMPq::Rmpq_div_z($f_k, $ck, $fact);

            # Accumulate current basis expansion into master polynomial
            for my $i (0 .. $#P) {
                $master_poly[$i] //= Math::GMPq::Rmpq_init();
                Math::GMPq::Rmpq_mul($term, $f_k, $P[$i]);
                Math::GMPq::Rmpq_add($master_poly[$i], $master_poly[$i], $term);
            }
        }

        # Progressively build the binomial basis: P_{k+1}(x) = P_k(x) * (x - (offset + k))
        if ($k < $#c) {

            # fact = fact * (k + 1)
            Math::GMPz::Rmpz_mul_ui($fact, $fact, $k + 1);

            # alpha = offset + k
            Math::GMPz::Rmpz_add_ui($alpha, $offset_mpz, $k);

            my @next_P;

            # next_P[0] = -alpha * P[0]
            $next_P[0] = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_mul_z($next_P[0], $P[0], $alpha);
            Math::GMPq::Rmpq_neg($next_P[0], $next_P[0]);

            # Linear combinations for intermediate polynomial indices
            for my $i (1 .. $k) {
                $next_P[$i] = $P[$i - 1];
                Math::GMPq::Rmpq_mul_z($tmp, $P[$i], $alpha);
                Math::GMPq::Rmpq_sub($next_P[$i], $next_P[$i], $tmp);
            }

            # Shift the highest degree term up: next_P[k+1] = P[k] * x
            $next_P[$k + 1] = $P[$k];
            @P = @next_P;
        }
    }

    my @poly_args;
    for my $i (0 .. $#master_poly) {
        next if (!defined $master_poly[$i] or Math::GMPq::Rmpq_sgn($master_poly[$i]) == 0);
        push(@poly_args, $i, Sidef::Types::Number::Number->new($master_poly[$i]));
    }

    return Sidef::Types::Number::Polynomial->new(@poly_args);
}

sub _pell_fundamental_unit {
    my ($d, $is_negative) = @_;

    if ($USE_PARI_GP and Math::GMPz::Rmpz_sizeinbase($d, 2) > 16) {

        my $code;
        my $d_str = Math::GMPz::Rmpz_get_str($d, 10);

        if ($is_negative) {
            $code = "iferr(my(u=quadunit(4*$d_str)); my(p=u.pol);
                           my(a=polcoef(p,0)); my(b=polcoef(p,1));
                           if(a^2-$d_str*b^2==-1, [a,b], []), E, \"\")" =~ s/\s+/ /gr;
        }
        else {
            $code = "iferr(my(u=quadunit(4*$d_str)); my(p=u.pol);
                            my(a=polcoef(p,0)); my(b=polcoef(p,1));
                            if(a^2-$d_str*b^2==-1, u=u^2; a=polcoef(u.pol,0);
                            b=polcoef(u.pol,1)); [a,b], E, \"\")" =~ s/\s+/ /gr;
        }

        if (my $res = _execute_pari_gp($code)) {
            if (my ($x_str, $y_str) = $res =~ /^\[(-?\d+),\s+(-?\d+)\]\z/) {
                my $U = Math::GMPz::Rmpz_init_set_str($x_str, 10);
                my $V = Math::GMPz::Rmpz_init_set_str($y_str, 10);
                Math::GMPz::Rmpz_abs($U, $U);
                Math::GMPz::Rmpz_abs($V, $V);
                return ($U, $V);
            }
            return ();
        }
    }

    state $a0 = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sqrt($a0, $d);

    state $P = Math::GMPz::Rmpz_init_nobless();
    state $Q = Math::GMPz::Rmpz_init_nobless();
    state $A = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($P, 0);
    Math::GMPz::Rmpz_set_ui($Q, 1);
    Math::GMPz::Rmpz_set($A, $a0);

    state $h2 = Math::GMPz::Rmpz_init_nobless();
    state $h1 = Math::GMPz::Rmpz_init_nobless();
    state $k2 = Math::GMPz::Rmpz_init_nobless();
    state $k1 = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($h2, 0);
    Math::GMPz::Rmpz_set_ui($h1, 1);
    Math::GMPz::Rmpz_set_ui($k2, 1);
    Math::GMPz::Rmpz_set_ui($k1, 0);

    state $h = Math::GMPz::Rmpz_init_nobless();
    state $k = Math::GMPz::Rmpz_init_nobless();

    state $next_P = Math::GMPz::Rmpz_init_nobless();
    state $next_Q = Math::GMPz::Rmpz_init_nobless();

    my $max = Math::GMPz::Rmpz_get_d($a0);
    $max = CORE::int(4 * $max * CORE::log($max) + 10);

    for (my $i = 0 ; $i <= $max ; ++$i) {
        Math::GMPz::Rmpz_mul($h, $A, $h1);
        Math::GMPz::Rmpz_add($h, $h, $h2);

        Math::GMPz::Rmpz_mul($k, $A, $k1);
        Math::GMPz::Rmpz_add($k, $k, $k2);

        Math::GMPz::Rmpz_mul($next_P, $A, $Q);
        Math::GMPz::Rmpz_sub($next_P, $next_P, $P);

        Math::GMPz::Rmpz_mul($next_Q, $next_P, $next_P);
        Math::GMPz::Rmpz_sub($next_Q, $d, $next_Q);
        Math::GMPz::Rmpz_divexact($next_Q, $next_Q, $Q);

        if (Math::GMPz::Rmpz_cmp_ui($next_Q, 1) == 0) {
            my $sgn = ($i % 2 == 0) ? -1 : 1;

            if ($is_negative) {
                if ($sgn == -1) {
                    my $ret_h = Math::GMPz::Rmpz_init_set($h);
                    my $ret_k = Math::GMPz::Rmpz_init_set($k);
                    return ($ret_h, $ret_k);
                }
            }
            else {
                if ($sgn == 1) {
                    my $ret_h = Math::GMPz::Rmpz_init_set($h);
                    my $ret_k = Math::GMPz::Rmpz_init_set($k);
                    return ($ret_h, $ret_k);
                }
            }
        }

        Math::GMPz::Rmpz_set($P, $next_P);
        Math::GMPz::Rmpz_set($Q, $next_Q);

        Math::GMPz::Rmpz_add($A, $a0, $P);
        Math::GMPz::Rmpz_div($A, $A, $Q);

        Math::GMPz::Rmpz_set($h2, $h1);
        Math::GMPz::Rmpz_set($h1, $h);

        Math::GMPz::Rmpz_set($k2, $k1);
        Math::GMPz::Rmpz_set($k1, $k);
    }
}

sub _reduce_pell_solution {
    my ($x, $y, $u1, $v1, $d) = @_;

    # Standardize to x >= 0
    if (Math::GMPz::Rmpz_sgn($x) < 0) {
        Math::GMPz::Rmpz_neg($x, $x);
        Math::GMPz::Rmpz_neg($y, $y);
    }

    state $x_next = Math::GMPz::Rmpz_init_nobless();
    state $y_next = Math::GMPz::Rmpz_init_nobless();
    state $dy     = Math::GMPz::Rmpz_init_nobless();

    while (1) {
        my $sgn_y = Math::GMPz::Rmpz_sgn($y);
        last if $sgn_y == 0;

        if ($sgn_y > 0) {
            Math::GMPz::Rmpz_mul($x_next, $x,  $u1);
            Math::GMPz::Rmpz_mul($dy,     $d,  $y);
            Math::GMPz::Rmpz_mul($dy,     $dy, $v1);
            Math::GMPz::Rmpz_sub($x_next, $x_next, $dy);

            Math::GMPz::Rmpz_mul($y_next, $y, $u1);
            Math::GMPz::Rmpz_mul($dy,     $x, $v1);
            Math::GMPz::Rmpz_sub($y_next, $y_next, $dy);
        }
        else {
            Math::GMPz::Rmpz_mul($x_next, $x,  $u1);
            Math::GMPz::Rmpz_mul($dy,     $d,  $y);
            Math::GMPz::Rmpz_mul($dy,     $dy, $v1);
            Math::GMPz::Rmpz_add($x_next, $x_next, $dy);

            Math::GMPz::Rmpz_mul($y_next, $y, $u1);
            Math::GMPz::Rmpz_mul($dy,     $x, $v1);
            Math::GMPz::Rmpz_add($y_next, $y_next, $dy);
        }

        if (Math::GMPz::Rmpz_sgn($x_next) < 0) {
            Math::GMPz::Rmpz_neg($x_next, $x_next);
            Math::GMPz::Rmpz_neg($y_next, $y_next);
        }

        if (Math::GMPz::Rmpz_cmp($x_next, $x) < 0) {
            Math::GMPz::Rmpz_set($x, $x_next);
            Math::GMPz::Rmpz_set($y, $y_next);
        }
        else {
            last;
        }
    }
}

sub _sort_pell_solutions {
    my (@sols) = @_;
    my %seen;
    @sols = grep {
        my $key = join(';', Math::GMPz::Rmpz_get_str(${$_->[0]}, 16), Math::GMPz::Rmpz_get_str(${$_->[1]}, 16));
        !$seen{$key}++
    } @sols;
    @sols = sort { (${$a->[0]} <=> ${$b->[0]}) || (${$b->[1]} <=> ${$a->[1]}) } @sols;
    return \@sols;
}

sub solve_pell {
    my ($d_in, $n_in) = @_;

    # Solution in integers to `x^2 - d*y^2 = n`
    # where `d` and `n` are provided (n=1 by default).

    my $d = _any2mpz($$d_in) // return _array();

    # Default to Classical Pell (n = 1) if not provided
    my $n = defined($n_in) ? do { _any2mpz($$n_in) // return _array() } : $ONE;

    # No solutions for d <= 0 or if d is a perfect square
    if (Math::GMPz::Rmpz_sgn($d) <= 0 or Math::GMPz::Rmpz_perfect_square_p($d)) {
        return _array();
    }

    # x^2 - d*y^2 = 0 implies x = y = 0 since d is non-square
    if (Math::GMPz::Rmpz_sgn($n) == 0) {
        my $zero1 = Math::GMPz::Rmpz_init_set_ui(0);
        my $zero2 = Math::GMPz::Rmpz_init_set_ui(0);
        return _array([_array([bless(\$zero1), bless(\$zero2)])]);
    }

    # --- Classical Pell (N = 1 or N = -1) via Continued Fractions ---
    if (Math::GMPz::Rmpz_cmpabs_ui($n, 1) == 0) {
        my ($x, $y) = _pell_fundamental_unit($d, Math::GMPz::Rmpz_sgn($n) < 0);
        $x // return _array();
        $y // return _array();
        return _array(_array((bless \$x), (bless \$y)));
    }

    my $abs_n = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_abs($abs_n, $n);

    my ($u1, $v1) = _pell_fundamental_unit($d);

    # --- FAST PATH: PARI/GP ---
    if ($USE_PARI_GP && (Math::GMPz::Rmpz_sizeinbase($d, 2) > 16 or Math::GMPz::Rmpz_sizeinbase($abs_n, 2) > 32)) {
        my $d_str = Math::GMPz::Rmpz_get_str($d, 10);
        my $n_str = Math::GMPz::Rmpz_get_str($n, 10);
        my $code  = "iferr(my(v=qfbsolve(Qfb(1,0,-$d_str), $n_str, 3)); if(#v, v, \"\"), E, \"\")";

        if (my $res = _execute_pari_gp($code)) {
            $res =~ /[0-9]/ or return _array();    # no solution exists

            my @sols;
            while ($res =~ /\[(-?\d+),\s*(-?\d+)\]/g) {
                my $x = Math::GMPz::Rmpz_init_set_str($1, 10);
                my $y = Math::GMPz::Rmpz_init_set_str($2, 10);
                _reduce_pell_solution($x, $y, $u1, $v1, $d);
                push @sols, _array([bless(\$x), bless(\$y)]);
            }

            if (@sols) {
                return _array(_sort_pell_solutions(@sols));
            }
        }
    }

    # --- Arbitrary N Path (LMM Algorithm) ---
    my @sq_divs = @{(bless \$abs_n)->square_divisors};

    state $sqrt_d = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sqrt($sqrt_d, $d);

    # Pre-allocate iteration variables to avoid GC thrashing in tight loops
    state $P_var  = Math::GMPz::Rmpz_init_nobless();
    state $Q_var  = Math::GMPz::Rmpz_init_nobless();
    state $X_var  = Math::GMPz::Rmpz_init_nobless();
    state $Y_var  = Math::GMPz::Rmpz_init_nobless();
    state $a_var  = Math::GMPz::Rmpz_init_nobless();
    state $P_new  = Math::GMPz::Rmpz_init_nobless();
    state $Q_new  = Math::GMPz::Rmpz_init_nobless();
    state $X_new  = Math::GMPz::Rmpz_init_nobless();
    state $Y_new  = Math::GMPz::Rmpz_init_nobless();
    state $x2     = Math::GMPz::Rmpz_init_nobless();
    state $y2     = Math::GMPz::Rmpz_init_nobless();
    state $diff   = Math::GMPz::Rmpz_init_nobless();
    state $dy     = Math::GMPz::Rmpz_init_nobless();
    state $z_var  = Math::GMPz::Rmpz_init_nobless();
    state $g      = Math::GMPz::Rmpz_init_nobless();
    state $m      = Math::GMPz::Rmpz_init_nobless();
    state $half_m = Math::GMPz::Rmpz_init_nobless();

    my $abs_m = Math::GMPz::Rmpz_init();

    my @solutions;
    foreach my $s_obj (@sq_divs) {
        my $s = _any2mpz($$s_obj);

        Math::GMPz::Rmpz_sqrt($g, $s);
        Math::GMPz::Rmpz_divexact($m, $n, $s);    # m = n / s
        Math::GMPz::Rmpz_abs($abs_m, $m);

        my @roots = @{(bless \$d)->sqrtmod_all(bless \$abs_m)};
        @roots || next;

        Math::GMPz::Rmpz_tdiv_q_2exp($half_m, $abs_m, 1);

        foreach my $r (@roots) {
            Math::GMPz::Rmpz_set_str($z_var, "$$r", 10);

            # Standardize z to the LMM bounds: (-|m|/2, |m|/2]
            if (Math::GMPz::Rmpz_cmp($z_var, $half_m) > 0) {
                Math::GMPz::Rmpz_sub($z_var, $z_var, $abs_m);
            }

            # Initialize P = z, Q = (z^2 - d) / m
            Math::GMPz::Rmpz_set($P_var, $z_var);
            Math::GMPz::Rmpz_mul($Q_var, $z_var, $z_var);
            Math::GMPz::Rmpz_sub($Q_var, $Q_var, $d);
            Math::GMPz::Rmpz_divexact($Q_var, $Q_var, $m);

            Math::GMPz::Rmpz_set($X_var, $z_var);
            Math::GMPz::Rmpz_set_ui($Y_var, 1);

            my %seen;

            while (1) {

                # Verification step: if X^2 - d*Y^2 == m, we found a primitive solution
                Math::GMPz::Rmpz_mul($x2, $X_var, $X_var);
                Math::GMPz::Rmpz_mul($y2, $Y_var, $Y_var);
                Math::GMPz::Rmpz_mul($y2, $y2,    $d);
                Math::GMPz::Rmpz_sub($diff, $x2, $y2);

                if (Math::GMPz::Rmpz_cmp($diff, $m) == 0) {

                    # Scale primitive solution by factor g
                    my $final_x = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($final_x, $X_var, $g);

                    my $final_y = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($final_y, $Y_var, $g);

                    # Reduce to fundamental class solution
                    _reduce_pell_solution($final_x, $final_y, $u1, $v1, $d);

                    push @solutions, _array([bless(\$final_x), bless(\$final_y)]);
                    push @solutions, _array([bless(\$final_x), bless(\$final_y)->neg]);
                    last;
                }

                # Cycle detection
                my $p_str = Math::GMPz::Rmpz_get_str($P_var, 16);
                my $q_str = Math::GMPz::Rmpz_get_str($Q_var, 16);
                last if $seen{"$p_str:$q_str"}++;

                # a = floor((P + sqrt(d)) / Q)
                Math::GMPz::Rmpz_add($a_var, $P_var, $sqrt_d);
                Math::GMPz::Rmpz_fdiv_q($a_var, $a_var, $Q_var);

                # P_new = a * Q - P
                Math::GMPz::Rmpz_mul($P_new, $a_var, $Q_var);
                Math::GMPz::Rmpz_sub($P_new, $P_new, $P_var);

                # Q_new = (d - P_new^2) / Q
                Math::GMPz::Rmpz_mul($Q_new, $P_new, $P_new);
                Math::GMPz::Rmpz_sub($Q_new, $d, $Q_new);
                Math::GMPz::Rmpz_divexact($Q_new, $Q_new, $Q_var);

                # X_new = (P_new * X + d * Y) / Q
                Math::GMPz::Rmpz_mul($X_new, $P_new, $X_var);
                Math::GMPz::Rmpz_mul($dy,    $d,     $Y_var);
                Math::GMPz::Rmpz_add($X_new, $X_new, $dy);
                Math::GMPz::Rmpz_divexact($X_new, $X_new, $Q_var);

                # Y_new = (P_new * Y + X) / Q
                Math::GMPz::Rmpz_mul($Y_new, $P_new, $Y_var);
                Math::GMPz::Rmpz_add($Y_new, $Y_new, $X_var);
                Math::GMPz::Rmpz_divexact($Y_new, $Y_new, $Q_var);

                Math::GMPz::Rmpz_set($P_var, $P_new);
                Math::GMPz::Rmpz_set($Q_var, $Q_new);
                Math::GMPz::Rmpz_set($X_var, $X_new);
                Math::GMPz::Rmpz_set($Y_var, $Y_new);
            }
        }
    }

    _array(_sort_pell_solutions(@solutions));
}

sub solve_lcg {
    my ($n, $r, $m) = @_;

    # Solve: n*x == r (mod m)

    ref($r) eq __PACKAGE__ or _valid(\$r);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = _any2mpz($$n) // goto &nan;
    $r = _any2mpz($$r) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    my $g = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_gcd($g, $n, $m);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) != 0) {

        # No solution exists if `r` is NOT divisible by `gcd(n,m)`
        Math::GMPz::Rmpz_divisible_p($r, $g) || goto &nan;

        $n = Math::GMPz::Rmpz_init_set($n);
        $r = Math::GMPz::Rmpz_init_set($r);
        $m = Math::GMPz::Rmpz_init_set($m);

        Math::GMPz::Rmpz_divexact($n, $n, $g);
        Math::GMPz::Rmpz_divexact($r, $r, $g);
        Math::GMPz::Rmpz_divexact($m, $m, $g);
    }

    Math::GMPz::Rmpz_invert($g, $n, $m);
    Math::GMPz::Rmpz_mul($g, $g, $r);
    Math::GMPz::Rmpz_mod($g, $g, $m);

    bless \$g;
}

*solve_linear_congruence = \&solve_lcg;

sub solve_lcg_all {
    my ($n, $r, $m) = @_;

    if ($n->is_div($m)) {
        return $r->is_div($m) ? $m->range->to_a : _array();
    }

    $n = _big2istr($$n) // return _array();
    $r = _big2istr($$r) // return _array();
    $m = _big2istr($$m) // return _array();

    my ($u_str, $v_str, $g_str) = Math::Prime::Util::GMP::gcdext($n, $m);

    if (Math::Prime::Util::GMP::modint($r, $g_str) ne '0') {
        return _array();
    }

    my $orig_m = Math::GMPz::Rmpz_init_set_str($m,     10);
    my $g_val  = Math::GMPz::Rmpz_init_set_str($g_str, 10);

    state $r_val = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_str($r_val, $r, 10);
    Math::GMPz::Rmpz_divexact($r_val, $r_val, $g_val);

    state $m_val = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_str($m_val, $m, 10);
    Math::GMPz::Rmpz_divexact($m_val, $m_val, $g_val);

    my $u_val = Math::GMPz::Rmpz_init_set_str($u_str, 10);
    state $z = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($z, $u_val, $r_val);

    my @solutions;
    my $limit = Math::GMPz::Rmpz_get_ui($g_val);    # Safe assuming g isn't cryptographically huge

    # Pre-allocate loop variables
    state $term = Math::GMPz::Rmpz_init_nobless();

    for my $t (0 .. $limit - 1) {
        Math::GMPz::Rmpz_mul_ui($term, $m_val, $t);
        Math::GMPz::Rmpz_add($term, $z, $term);
        Math::GMPz::Rmpz_mod($term, $term, $orig_m);

        # Clone the result into the Sidef object
        my $r =
            Math::GMPz::Rmpz_fits_ulong_p($term)
          ? Math::GMPz::Rmpz_get_ui($term)
          : Math::GMPz::Rmpz_init_set($term);
        push @solutions, $r;
    }

    _array([map { bless \$_ } sort { $a <=> $b } @solutions]);
}

*linear_congruence = \&solve_lcg_all;

sub sqrt_cfrac_period_each {
    my ($n, $block, $max) = @_;

    $n   = _any2mpz($$n) // return ZERO;
    $max = defined($max) ? CORE::int($max) : (0 + 'inf');

    Math::GMPz::Rmpz_sgn($n) < 0
      and return ZERO;

    my $x = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sqrt($x, $n);

    Math::GMPz::Rmpz_perfect_square_p($n)
      and return ZERO;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {

        $n = Math::GMPz::Rmpz_get_ui($n);
        $x = Math::GMPz::Rmpz_get_ui($x);

        my $y = $x;
        my $z = 1;
        my $r = $x + $x;

        my $count = 0;

        for (; $count < $max ; ++$count) {

            $y = $r * $z - $y;
            $z = CORE::int(($n - $y * $y) / $z);
            $r = CORE::int(($x + $y) / $z);

            $block->run(_set_int($r));

            if ($z == 1) {
                ++$count;
                last;
            }
        }

        return _set_int($count);
    }

    my $y = Math::GMPz::Rmpz_init_set($x);
    my $z = Math::GMPz::Rmpz_init_set_ui(1);
    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_add($r, $x, $x);    # r = x+x

    my $count = 0;
    for (; $count < $max ; ++$count) {

        my $t = Math::GMPz::Rmpz_init();

        # y = (r*z - y)
        Math::GMPz::Rmpz_submul($y, $r, $z);    # y = y - t*z
        Math::GMPz::Rmpz_neg($y, $y);           # y = -y

        # z = ((n - y*y) / z)
        Math::GMPz::Rmpz_mul($t, $y, $y);         # t = y*y
        Math::GMPz::Rmpz_sub($t, $n, $t);         # t = n-t
        Math::GMPz::Rmpz_divexact($z, $t, $z);    # z = t/z

        # t = floor((x + y) / z)
        Math::GMPz::Rmpz_add($t, $x, $y);         # t = x+y
        Math::GMPz::Rmpz_div($t, $t, $z);         # t = floor(t/z)

        $r = $t;
        $block->run(bless \$t);

        if (Math::GMPz::Rmpz_cmp_ui($z, 1) == 0) {
            ++$count;
            last;
        }
    }

    return _set_int($count);
}

sub sqrt_cfrac {
    my ($n, $max) = @_;
    $n->sqrt_cfrac_period($max)->unshift($n->isqrt);
}

sub sqrt_cfrac_period {
    my ($n, $max) = @_;

    my @cfrac;

    $n->sqrt_cfrac_period_each(
        Sidef::Types::Block::Block->new(
            code => sub {
                push @cfrac, $_[0];
            }
        ),
        $max
    );

    _array(\@cfrac);
}

sub sqrt_cfrac_period_len {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    return ZERO if Math::GMPz::Rmpz_perfect_square_p($n);

    goto &nan if Math::GMPz::Rmpz_sgn($n) < 0;

    my $t = Math::GMPz::Rmpz_init();
    my $x = Math::GMPz::Rmpz_init();
    my $z = Math::GMPz::Rmpz_init_set_ui(1);

    Math::GMPz::Rmpz_sqrt($x, $n);

    my $y = Math::GMPz::Rmpz_init_set($x);

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {

        $n = Math::GMPz::Rmpz_get_ui($n);
        $x = Math::GMPz::Rmpz_get_ui($x);

        my $y = $x;
        my $z = 1;
        my $r = $x + $x;

        my $period = 0;

        do {
            $y = $r * $z - $y;
            $z = CORE::int(($n - $y * $y) / $z);
            $r = CORE::int(($x + $y) / $z);
            ++$period;
        } until ($z == 1);

        return _set_int($period);
    }

    my $period = 0;

    do {

        # y = floor((x+y)/z)*z - y
        Math::GMPz::Rmpz_add($t, $x, $y);    # t = x+y
        Math::GMPz::Rmpz_div($t, $t, $z);    # t = floor(t/z)
        Math::GMPz::Rmpz_mul($t, $t, $z);    # t = t*z
        Math::GMPz::Rmpz_sub($y, $t, $y);    # y = t-y

        # z = (n - y*y)/z
        Math::GMPz::Rmpz_mul($t, $y, $y);         # t = y*y
        Math::GMPz::Rmpz_sub($t, $n, $t);         # t = n-t
        Math::GMPz::Rmpz_divexact($z, $t, $z);    # z = t/z

        ++$period;

    } until (Math::GMPz::Rmpz_cmp_ui($z, 1) == 0);

    _set_int($period);
}

sub convergents {
    my ($x, $n) = @_;

    my @cfrac = @{$x->as_cfrac($n)};

    my ($n1, $n2) = (0, 1);
    my ($d1, $d2) = (1, 0);

    my @convergents;
    foreach my $z (map { $$_ } @cfrac) {

        ($n1, $n2) = ($n2, __add__(__mul__($n2, $z), $n1));
        ($d1, $d2) = ($d2, __add__(__mul__($d2, $z), $d1));

        push @convergents, bless \__div__($n2, $d2);
    }

    _array(\@convergents);
}

sub farey {
    my ($n) = @_;

    $n = _any2ui($$n) // return undef;

    my ($A, $B, $C, $D) = (0, 1, 1, $n);

    my @list = (ZERO);

    while ($C >= 0 and $C <= $n) {
        my $k =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n + $B, $D)
          : Math::Prime::Util::GMP::divint($n + $B, $D);
        ($A, $B, $C, $D) = ($C, $D, $k * $C - $A, $k * $D - $B);
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_ui($q, $A, $B);
        push @list, bless \$q;
    }

    _array(\@list);
}

sub farey_neighbors {
    my ($n, $pq) = @_;

    $n  = _any2mpz($$n)  // return (undef, undef);
    $pq = _any2mpq($$pq) // return (undef, undef);

    Math::GMPq::Rmpq_sgn($pq) > 0          or return (undef, undef);
    Math::GMPq::Rmpq_cmp_ui($pq, 1, 1) < 0 or return (undef, undef);

    my $p = Math::GMPz::Rmpz_init();
    my $q = Math::GMPz::Rmpz_init();

    Math::GMPq::Rmpq_get_num($p, $pq);
    Math::GMPq::Rmpq_get_den($q, $pq);

    my $pl = Math::GMPz::Rmpz_init();
    my $ql = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_invert($pl, $q, $p);
    Math::GMPz::Rmpz_neg($pl, $pl);
    Math::GMPz::Rmpz_mod($pl, $pl, $p);
    Math::GMPz::Rmpz_invert($ql, $p, $q);

    my $pr = Math::GMPz::Rmpz_init();
    my $qr = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sub($pr, $p, $pl);
    Math::GMPz::Rmpz_sub($qr, $q, $ql);

    my $fl = Math::GMPz::Rmpz_init();
    my $fr = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sub($fl, $n, $ql);
    Math::GMPz::Rmpz_sub($fr, $n, $qr);
    Math::GMPz::Rmpz_div($fl, $fl, $q);
    Math::GMPz::Rmpz_div($fr, $fr, $q);

    my $q1 = Math::GMPq::Rmpq_init();
    my $q2 = Math::GMPq::Rmpq_init();

    Math::GMPz::Rmpz_addmul($pl, $p, $fl);
    Math::GMPz::Rmpz_addmul($ql, $q, $fl);

    Math::GMPz::Rmpz_addmul($pr, $p, $fr);
    Math::GMPz::Rmpz_addmul($qr, $q, $fr);

    Math::GMPq::Rmpq_set_num($q1, $pl);
    Math::GMPq::Rmpq_set_den($q1, $ql);

    Math::GMPq::Rmpq_set_num($q2, $pr);
    Math::GMPq::Rmpq_set_den($q2, $qr);

    return ((bless \$q1), (bless \$q2));
}

sub egypt_greedy {
    my ($pq) = @_;

    $pq = _any2mpq($$pq) // return undef;

    my $n = Math::GMPz::Rmpz_init();
    my $d = Math::GMPz::Rmpz_init();

    Math::GMPq::Rmpq_get_num($n, $pq);
    Math::GMPq::Rmpq_get_den($d, $pq);

    my @list;

    state $g = Math::GMPz::Rmpz_init_nobless();

    while (Math::GMPz::Rmpz_cmp_ui($n, 1) != 0) {

        my $x = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_div($x, $d, $n);
        Math::GMPz::Rmpz_add_ui($x, $x, 1);

        push @list, $x;

        Math::GMPz::Rmpz_mul($n, $n, $x);
        Math::GMPz::Rmpz_sub($n, $n, $d);
        Math::GMPz::Rmpz_mul($d, $d, $x);
        Math::GMPz::Rmpz_gcd($g, $n, $d);

        if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
            Math::GMPz::Rmpz_divexact($n, $n, $g);
            Math::GMPz::Rmpz_divexact($d, $d, $g);
        }
    }

    push @list, $d;
    _array([map { bless \$_ } @list]);
}

sub dump {
    my ($x) = @_;

    $x = $$x;
    Sidef::Types::String::String->new(
                                       !ref($x)                 ? $x
                                      : ref($x) eq 'Math::GMPz' ? Math::GMPz::Rmpz_get_str($x, 10)
                                      : ref($x) eq 'Math::GMPq' ? Math::GMPq::Rmpq_get_str($x, 10)
                                      :                           __stringify__($x)
                                     );
}

*stringify = \&dump;
*pretty    = \&dump;

sub to_str {
    my ($x) = @_;
    Sidef::Types::String::String->new(__stringify__($$x));
}

*to_s = \&to_str;

sub to_num {
    $_[0];
}

*to_n = \&to_num;

sub as_bin {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return Sidef::Types::String::String->new((($x < 0) ? '-' : '') . CORE::sprintf('%b', CORE::abs($x)));
    }

    Sidef::Types::String::String->new(Math::GMPz::Rmpz_get_str((_any2mpz($x) // return undef), 2));
}

sub as_oct {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return Sidef::Types::String::String->new((($x < 0) ? '-' : '') . CORE::sprintf('%o', CORE::abs($x)));
    }

    Sidef::Types::String::String->new(Math::GMPz::Rmpz_get_str((_any2mpz($x) // return undef), 8));
}

sub as_hex {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return Sidef::Types::String::String->new((($x < 0) ? '-' : '') . CORE::sprintf('%x', CORE::abs($x)));
    }

    Sidef::Types::String::String->new(Math::GMPz::Rmpz_get_str((_any2mpz($x) // return undef), 16));
}

sub bits {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        return _array([map { $_ ? ONE : ZERO } split(//, CORE::sprintf('%b', CORE::abs($x)))]);
    }

    $x = _any2mpz($x) // return _array();
    my $bin = Math::GMPz::Rmpz_get_str($x, 2);
    $bin = substr($bin, 1) if substr($bin, 0, 1) eq '-';
    _array([map { $_ ? ONE : ZERO } split(//, $bin)]);
}

sub digital_root {
    my ($n, $base) = @_;

    # Formula:
    #   digital_root(n,b) = n - (b-1)*floor((n-1)/(b-1))

    $n = _any2mpz($$n) // goto &nan;

    if (defined($base)) {
        ref($base) eq __PACKAGE__ or _valid(\$base);
        $base = _any2mpz($$base) // goto &nan;
        Math::GMPz::Rmpz_cmp_ui($base, 1) > 0 or goto &nan;
    }
    else {
        $base = $TEN;
    }

    Math::GMPz::Rmpz_sgn($n) || return ZERO;

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($r, $n,    1);
    Math::GMPz::Rmpz_sub_ui($t, $base, 1);
    Math::GMPz::Rmpz_mod($r, $r, $t);
    Math::GMPz::Rmpz_add_ui($r, $r, 1);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub expnorm {
    my ($n, $base) = @_;

    # Based on the following identities (where b is the base):
    #   f / exp(log(b)*floor(log(f) / log(b) + 1)) = exp(log(f) - log(b)*floor(log(f) / log(b) + 1))
    #                                              = b^(-1 - floor(log(f^(1/log(b))))) * f
    #                                              = exp(log(b)*(-1 - floor(log(f) / log(b))) + log(f))

    $n = _any2mpfr_mpc($$n) // goto &nan;

    my $log;

    if (defined($base)) {
        ref($base) eq __PACKAGE__ or _valid(\$base);
        $base = _any2mpfr_mpc($$base) // goto &nan;
        $log  = __log__($base);
    }
    else {
        state $ten = _any2mpfr(10);
        $log = __log__($ten);
    }

    if (ref($log) eq 'Math::MPC' and ref($n) eq 'Math::MPFR') {
        $n = _mpfr2mpc($n);
    }

    if (ref($n) eq 'Math::MPFR' and ref($log) eq 'Math::MPFR') {

        my $exp = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPFR::Rmpfr_div($exp, $n, $log, $ROUND);
        Math::MPFR::Rmpfr_floor($exp, $exp);
        Math::MPFR::Rmpfr_add_ui($exp, $exp, 1, $ROUND);
        Math::MPFR::Rmpfr_mul($exp, $exp, $log, $ROUND);
        Math::MPFR::Rmpfr_sub($exp, $n, $exp, $ROUND);
        Math::MPFR::Rmpfr_exp($exp, $exp, $ROUND);

        return bless \$exp;
    }

    my $exp = Math::MPC::Rmpc_init2(CORE::int($PREC));

    ref($log) eq 'Math::MPFR'
      ? Math::MPC::Rmpc_div_fr($exp, $n, $log, $ROUND)
      : Math::MPC::Rmpc_div($exp, $n, $log, $ROUND);

    my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPC::RMPC_RE($real, $exp);
    Math::MPC::RMPC_IM($imag, $exp);

    Math::MPFR::Rmpfr_floor($real, $real);
    Math::MPFR::Rmpfr_floor($imag, $imag);

    Math::MPC::Rmpc_set_fr_fr($exp, $real, $imag, $ROUND);

    Math::MPC::Rmpc_add_ui($exp, $exp, 1, $ROUND);

    ref($log) eq 'Math::MPFR'
      ? Math::MPC::Rmpc_mul_fr($exp, $exp, $log, $ROUND)
      : Math::MPC::Rmpc_mul($exp, $exp, $log, $ROUND);

    Math::MPC::Rmpc_sub($exp, $n, $exp, $ROUND);
    Math::MPC::Rmpc_exp($exp, $exp, $ROUND);

    bless \$exp;
}

sub digits {
    my ($n, $k) = @_;

    $n = $$n;

    if (FAST_MODE and !defined($k) and !ref($n)) {
        $n == 0 and return _array([ZERO]);
        my @digits = CORE::reverse(
                                   HAS_PRIME_UTIL
                                   ? Math::Prime::Util::todigits(CORE::abs($n))
                                   : split(//, CORE::abs($n))
                                  );
        return _array([map { bless \$_ } @digits]);
    }

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = $$k;

        if (FAST_MODE and !ref($k) and !ref($n)) {

            $n = CORE::abs($n);

            $k >= 2 or return _array();
            $n == 0 and return _array([ZERO]);
            $n < $k and return _array([bless \$n]);

            my @digits;

            if (HAS_PRIME_UTIL and $k < 2147483647) {
                @digits = Math::Prime::Util::todigits($n, $k);
            }
            elsif ($k == 2 or $k == 8) {
                @digits = split(//, sprintf(($k == 2 ? '%b' : '%o'), $n));
            }
            elsif ($k == 10) {
                @digits = split(//, $n);
            }
            elsif ($k == 16) {
                @digits = @DIGITS_36{split(//, sprintf('%x', $n))};
            }

            if (@digits) {
                return _array([map { bless \$_ } CORE::reverse(@digits)]);
            }
        }

        $k = _any2mpz($k) // return _array();

        # Not defined for k <= 1
        if (Math::GMPz::Rmpz_cmp_ui($k, 1) <= 0) {
            return _array();
        }
    }

    $n = _any2mpz($n) // return _array();

    my $sgn = Math::GMPz::Rmpz_sgn($n);

    if ($sgn == 0) {
        return _array([ZERO]);
    }
    elsif ($sgn < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);
    }

#<<<
    if (!defined($k) or Math::GMPz::Rmpz_cmp_ui($k, 62) <= 0) {
        $k = defined($k) ? Math::GMPz::Rmpz_get_ui($k) : 10;
        return _array([
           map { bless \$_ }
            ($k <= 36)
                ? @DIGITS_36{split(//, scalar CORE::reverse scalar Math::GMPz::Rmpz_get_str($n, $k))}
                : @DIGITS_62{split(//, scalar CORE::reverse scalar Math::GMPz::Rmpz_get_str($n, $k))}
        ]);
    }
#>>>

    # Subquadratic Algorithm 1.26 FastIntegerOutput from "Modern Computer Arithmetic v0.5.9"
    if (Math::GMPz::Rmpz_fits_ulong_p($k)) {

        my $A = $n;
        my $B = Math::GMPz::Rmpz_get_ui($k);

        # When B < 2^31, use Math::Prime::Util::GMP::todigits().
        if ($B < 2147483647) {
            return _array([map { bless \$_ } CORE::reverse(Math::Prime::Util::GMP::todigits(Math::GMPz::Rmpz_get_str($n, 10), $B))]);
        }

        # Power Tree Precomputation
        # We precompute a minimal list of successive squares: B, B^2, B^4, B^8, ...
        my @powers;
        my $p = Math::GMPz::Rmpz_init_set_ui($B);
        while (Math::GMPz::Rmpz_cmp($p, $A) <= 0) {
            push @powers, $p;
            my $next_p = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($next_p, $p, $p);
            $p = $next_p;
        }
        push @powers, $p if !@powers;

        my @Q_pool = map { Math::GMPz::Rmpz_init() } 0 .. $#powers;
        my @R_pool = map { Math::GMPz::Rmpz_init() } 0 .. $#powers;

        my @digits = map { bless \$_ } sub {
            my ($A, $k) = @_;

            # Cut the recursion early
            if (Math::GMPz::Rmpz_fits_ulong_p($A)) {
                my $v = Math::GMPz::Rmpz_get_ui($A);
                my ($m, @digits);
                while ($v) {
                    ($v, $m) =
                      HAS_PRIME_UTIL
                      ? Math::Prime::Util::divrem($v, $B)
                      : Math::Prime::Util::GMP::divrem($v, $B);
                    push @digits, $m;
                }
                return @digits;
            }

            # Trace down the precomputed base-power index
            while ($k > 0 and Math::GMPz::Rmpz_cmp($powers[$k], $A) > 0) {
                $k--;
            }

            my $Q = $Q_pool[$k];
            my $R = $R_pool[$k];
            Math::GMPz::Rmpz_divmod($Q, $R, $A, $powers[$k]);

            my $expected_digits = 1 << $k;

            my @right = __SUB__->($R, $k - 1);
            my @left  = __SUB__->($Q, $k - 1);

            push @right, (0) x ($expected_digits - scalar(@right));
            (@right, @left);
          }
          ->($A, $#powers);

        return _array(\@digits);
    }

    # This algorithm will be used only when base > ULONG_MAX
    my @digits;

    $n = Math::GMPz::Rmpz_init_set($n);    # copy

    while (Math::GMPz::Rmpz_sgn($n) > 0) {
        my $m = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_divmod($n, $m, $n, $k);
        push @digits, bless \$m;
    }

    _array(\@digits);
}

sub __digits2num__ {
    my ($base, $digits) = @_;

    # $base is a native integer >= 2
    # $digits is a non-empty array of native integers < base (msd first)

    if (HAS_PRIME_UTIL and $base < 2147483647 and CORE::log($base) * scalar(@$digits) < CORE::log(ULONG_MAX)) {
        return Math::Prime::Util::fromdigits($digits, $base);
    }

    if ($base <= 10) {
        return Math::GMPz::Rmpz_init_set_str(join('', @$digits), $base);
    }

    if ($base <= 36) {
        return Math::GMPz::Rmpz_init_set_str(join('', @FROM_DIGITS_36[@$digits]), $base);
    }

    if ($base <= 62) {
        return Math::GMPz::Rmpz_init_set_str(join('', @FROM_DIGITS_62[@$digits]), $base);
    }

    my @D   = CORE::reverse(@$digits);
    my $len = scalar(@D);

    if (CORE::log($base) * $len < CORE::log(ULONG_MAX)) {
        my $r = 0;
        my $B = 1;

        foreach my $d (@D) {
            $r += $B * $d;
            $B *= $base;
        }

        return $r;
    }

    # Digit Chunking
    # Find the maximum number of digits (K) that safely pack into a single native unsigned integer.
    # This prevents creating individual Math::GMPz objects for every single digit.
    my $K          = 1;
    my $chunk_base = $base;
    my $max_val    = CORE::int(ULONG_MAX / $base);
    while ($chunk_base < $max_val) {
        $chunk_base *= $base;
        $K++;
    }

    my @d;
    my $B;
    if ($K > 1 and $len > $K) {
        for (my $i = 0 ; $i < $len ; $i += $K) {
            my $r     = 0;
            my $b0    = 1;
            my $limit = $i + $K < $len ? $i + $K : $len;
            for my $j ($i .. $limit - 1) {
                $r  += $D[$j] * $b0;
                $b0 *= $base;
            }
            push @d, Math::GMPz::Rmpz_init_set_ui($r);
        }
        $B = Math::GMPz::Rmpz_init_set_ui($chunk_base);
    }
    else {
        @d = map { Math::GMPz::Rmpz_init_set_ui($_) } @D;
        $B = Math::GMPz::Rmpz_init_set_ui($base);
    }

    # Subquadratic Algorithm 1.25 FastIntegerInput from "Modern Computer Arithmetic v0.5.9"

    my $L = \@d;
    my $T = [];
    my $k = scalar(@$L);

    while ($k > 1) {
        @$T = ();
        my $limit = ($k >> 1) - 1;
        for my $i (0 .. $limit) {
            my $t = $L->[2 * $i];
            Math::GMPz::Rmpz_addmul($t, $L->[2 * $i + 1], $B);
            push(@$T, $t);
        }

        push(@$T, $L->[-1]) if ($k & 1);
        ($L, $T) = ($T, $L);
        $k = ($k >> 1) + ($k & 1);
        Math::GMPz::Rmpz_mul($B, $B, $B) if $k > 1;
    }

    return $L->[0];
}

sub digits2num {
    my ($base, $D) = @_;

    # Subquadratic Algorithm 1.25 FastIntegerInput from "Modern Computer Arithmetic v0.5.9"

    my @digits = @$D;
    @digits || return ZERO;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    _valid(\(@digits));

    $base   = $$base;
    @digits = map { $$_ } @digits;

    my $all_native = (!ref($base) and $base >= 2);

    if ($all_native) {
        foreach my $digit (@digits) {
            if (FAST_MODE and !ref($digit) and $digit >= 0 and $digit < $base) {
                ## ok
            }
            else {
                $all_native = 0;
                last;
            }
        }
    }

    if ($all_native) {
        return _set_int(__digits2num__($base, [CORE::reverse(@digits)]));
    }

    my $all_mpz = ref($base) eq 'Math::GMPz';

    if ($all_mpz) {
        foreach my $digit (@digits) {
            if (ref($digit) ne 'Math::GMPz') {
                $all_mpz = 0;
                last;
            }
        }
    }

    my $L = \@digits;

    if ($all_mpz and Math::GMPz::Rmpz_cmp_ui($base, 2) >= 0) {

        if (Math::GMPz::Rmpz_cmp_ui($base, 62) <= 0) {    # return faster for base in 2..62

            my $str      = '';
            my $optimize = 1;
            my $B        = Math::GMPz::Rmpz_get_ui($base);

            foreach my $digit (CORE::reverse(@digits)) {
                if (Math::GMPz::Rmpz_cmp_ui($digit, $B) < 0 and Math::GMPz::Rmpz_sgn($digit) >= 0) {
                    $str .= Math::GMPz::Rmpz_get_str($digit, $B);
                }
                else {
                    $optimize = 0;
                    last;
                }
            }

            if ($optimize) {
                return bless \(my $value = Math::GMPz::Rmpz_init_set_str($str, $B));
            }
        }

        my $B = Math::GMPz::Rmpz_init_set($base);

        # Selective In-Place Modification & Buffer Reuse
        # Elements are only cloned on the first pass to avoid mutating the user's input.
        # Subsequent iterations safely reuse scratchpad intermediate objects, saving ~50% of allocations.
        my $T          = [];
        my $k          = scalar(@digits);
        my $first_pass = 1;

        while ($k > 1) {
            @$T = ();
            my $limit = ($k >> 1) - 1;
            for my $i (0 .. $limit) {
                my $t = $first_pass ? Math::GMPz::Rmpz_init_set($L->[2 * $i]) : $L->[2 * $i];
                Math::GMPz::Rmpz_addmul($t, $L->[2 * $i + 1], $B);
                push(@$T, $t);
            }

            push(@$T, $L->[-1]) if ($k & 1);
            ($L, $T) = ($T, $L);
            $k          = ($k >> 1) + ($k & 1);
            $first_pass = 0;
            Math::GMPz::Rmpz_mul($B, $B, $B) if $k > 1;
        }

        return bless \($L->[0]);
    }

    my $B = $base;

    my $T = [];
    my $k = scalar(@digits);
    while ($k > 1) {
        @$T = ();
        my $limit = ($k >> 1) - 1;
        for my $i (0 .. $limit) {
            push(@$T, __add__($L->[2 * $i], __mul__($B, $L->[2 * $i + 1])));
        }

        push(@$T, $L->[-1]) if ($k & 1);
        ($L, $T) = ($T, $L);
        $k = ($k >> 1) + ($k & 1);
        $B = __mul__($B, $B) if $k > 1;
    }

    bless \($L->[0]);
}

*from_digits = \&digits2num;

sub digit {
    my ($n, $i, $k) = @_;

    ref($i) eq __PACKAGE__ or _valid(\$i);

    $n = _any2mpz($$n) // (return undef);
    $i = $$i;

    $i = _any2si($i) // (return undef) if ref($i);

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = _any2mpz($$k) // return undef;

        # Not defined for k <= 1
        if (Math::GMPz::Rmpz_cmp_ui($k, 1) <= 0) {
            return undef;
        }
    }
    else {
        $k = $TEN;
    }

    my $t = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init_set($n);

    my $sgn = Math::GMPz::Rmpz_sgn($u);

    if ($sgn == 0) {
        return ZERO;
    }
    elsif ($sgn < 0) {
        Math::GMPz::Rmpz_abs($u, $u);
    }

    if ($i < 0) {
        $i += __ilog__($u, $k) + 1;
        return undef if ($i < 0);
    }

    Math::GMPz::Rmpz_pow_ui($t, $k, $i);
    Math::GMPz::Rmpz_div($u, $u, $t);
    Math::GMPz::Rmpz_mod($u, $u, $k);

    bless \$u;
}

sub sumdigits {
    my ($n, $k) = @_;

    $n = $$n;

    if (FAST_MODE and !defined($k) and !ref($n)) {
        $n == 0 and return ZERO;
        return
          bless \(
                  my $value = List::Util::sum(
                                              HAS_PRIME_UTIL
                                              ? Math::Prime::Util::todigits(CORE::abs($n))
                                              : split(//, CORE::abs($n))
                                             )
                 );
    }

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = $$k;

        if (FAST_MODE and !ref($k) and !ref($n)) {

            $n = CORE::abs($n);

            $k >= 2 or return undef;
            $n == 0 and return ZERO;
            $n < $k and return _set_int($n);

            if (HAS_PRIME_UTIL and $k < 2147483647) {
                if ($k == 2) {
                    return bless \(my $value = Math::Prime::Util::hammingweight($n));
                }
                return bless \(my $value = List::Util::sum(Math::Prime::Util::todigits($n, $k)));
            }
            elsif ($k == 2 or $k == 8) {
                return bless \(my $value = List::Util::sum(split(//, sprintf(($k == 2 ? '%b' : '%o'), $n))));
            }
            elsif ($k == 10) {
                return bless \(my $value = List::Util::sum(split(//, $n)));
            }
            elsif ($k == 16) {
                return bless \(my $value = List::Util::sum(@DIGITS_36{split(//, sprintf('%x', $n))}));
            }
        }

        $k = _any2mpz($k) // return undef;

        # Not defined for k <= 1
        if (Math::GMPz::Rmpz_cmp_ui($k, 1) <= 0) {
            return undef;
        }
    }

    $n = _any2mpz($n) // return undef;

    my $sgn = Math::GMPz::Rmpz_sgn($n);

    if ($sgn == 0) {
        return ZERO;
    }
    elsif ($sgn < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);
    }

    if (!defined($k) or Math::GMPz::Rmpz_cmp_ui($k, 62) <= 0) {
        $k = defined($k) ? Math::GMPz::Rmpz_get_ui($k) : 10;

        return _set_int(scalar Math::GMPz::Rmpz_popcount($n)) if ($k == 2);

        if (Math::GMPz::Rmpz_sizeinbase($n, $k) <= 1e6) {
            if ($k <= 36) {
                return _set_int(List::Util::sum(@DIGITS_36{split(//, Math::GMPz::Rmpz_get_str($n, $k))}));
            }
            else {
                return _set_int(List::Util::sum(@DIGITS_62{split(//, Math::GMPz::Rmpz_get_str($n, $k))}));
            }
        }
        else {
            $k = Math::GMPz::Rmpz_init_set_ui($k);
        }
    }

    # Subquadratic Algorithm 1.26 FastIntegerOutput from "Modern Computer Arithmetic v0.5.9"
    if (Math::GMPz::Rmpz_fits_ulong_p($k)) {

        my $A = $n;
        my $B = Math::GMPz::Rmpz_get_ui($k);

        # When B < 2^31, use Math::Prime::Util::GMP::todigits().
        if ($B < 2147483647 and Math::GMPz::Rmpz_sizeinbase($n, 62) <= 1e6) {
            return _set_int(List::Util::sum(Math::Prime::Util::GMP::todigits(Math::GMPz::Rmpz_get_str($n, 10), $B)));
        }

        # Power Tree Precomputation
        # Precompute successive squares of the base B up to the magnitude of A.
        my @powers;
        my $p = Math::GMPz::Rmpz_init_set_ui($B);
        while (Math::GMPz::Rmpz_cmp($p, $A) <= 0) {
            push @powers, $p;
            my $next_p = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($next_p, $p, $p);
            $p = $next_p;
        }
        push @powers, $p if !@powers;

        # Preallocated Scratchpad Pool for Zero Tree Allocations
        # Since the recursion depth matches the number of precomputed powers, we can
        # instantiate unblessed temporary variables before entering the recursion tree.
        my @Q_pool = map { Math::GMPz::Rmpz_init() } 0 .. $#powers;
        my @R_pool = map { Math::GMPz::Rmpz_init() } 0 .. $#powers;

        my $total = sub {
            my ($A, $k) = @_;

            # Cut the recursion early
            if (Math::GMPz::Rmpz_fits_ulong_p($A)) {
                my $v = Math::GMPz::Rmpz_get_ui($A);
                my ($sum, $m) = (0);
                while ($v) {
                    ($v, $m) =
                      HAS_PRIME_UTIL
                      ? Math::Prime::Util::divrem($v, $B)
                      : Math::Prime::Util::GMP::divrem($v, $B);
                    $sum += $m;
                }
                return $sum;
            }

            while ($k > 0 and Math::GMPz::Rmpz_cmp($powers[$k], $A) > 0) {
                $k--;
            }

            # Map to the specific preallocated scratchpad variables for this depth level
            my $Q = $Q_pool[$k];
            my $R = $R_pool[$k];
            Math::GMPz::Rmpz_divmod($Q, $R, $A, $powers[$k]);

            __SUB__->($R, $k - 1) + __SUB__->($Q, $k - 1);
          }
          ->($A, $#powers);

        ($total < ULONG_MAX)
          && return _set_int($total);
    }

    # This algorithm will be used only for very large bases,
    # base > ULONG_MAX, or when the sum of digits exceeds ULONG_MAX.
    state $m = Math::GMPz::Rmpz_init_nobless();
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    $n = Math::GMPz::Rmpz_init_set($n);    # copy

    while (Math::GMPz::Rmpz_sgn($n) > 0) {
        Math::GMPz::Rmpz_divmod($n, $m, $n, $k);
        Math::GMPz::Rmpz_add($sum, $sum, $m);
    }

    bless \$sum;
}

*digits_sum = \&sumdigits;
*sum_digits = \&sumdigits;

sub is_pandigital {
    my ($n, $base) = @_;

    if (defined($base)) {
        ref($base) eq __PACKAGE__ or _valid(\$base);
    }
    else {
        $base = TEN;
    }

    # If a(n) is the smallest pandigital number to base n, then:
    # a(n) = (n^n-n)/(n-1)^2 + n^(n-2)*(n-1) - 1
    # https://en.wikipedia.org/wiki/Pandigital_number

    my $b_val = $base->numify;
    $b_val < 1 and return $FALSE;

    my $n_ilog2 = $n->ilog2->numify;

    if ($n_ilog2 < ($b_val - 2) * (CORE::log($b_val) / CORE::log(2))) {
        return $FALSE;
    }

    # Extremely fast bitmasking for bases up to INTSIZE (usually 64)
    # Avoids object creation, array allocation, and hash lookups entirely.
    if ($b_val < INTSIZE and $n_ilog2 < 1000) {
        state $q = Math::GMPz::Rmpz_init_nobless();

        # Safely copy into scratchpad so we don't mutate the user's object
        Math::GMPz::Rmpz_set($q, _any2mpz($$n) // return $FALSE);
        Math::GMPz::Rmpz_abs($q, $q) if Math::GMPz::Rmpz_sgn($q) < 0;

        my $mask   = 0;
        my $target = (1 << $b_val) - 1;

        while (Math::GMPz::Rmpz_sgn($q) > 0) {

            # Rmpz_fdiv_q_ui modifies $q and returns the remainder
            my $rem = Math::GMPz::Rmpz_fdiv_q_ui($q, $q, $b_val);
            $mask |= (1 << $rem);
            return $TRUE if $mask == $target;
        }

        return $FALSE;
    }

    # Fallback for massive bases (> 64)
    my %hash;
    @hash{@{$n->digits($base)}} = ();
    scalar(keys %hash) == $b_val ? $TRUE : $FALSE;
}

sub is_smith {
    my ($n, $base) = @_;

    $n    = $$n;
    $base = defined($base) ? do { ref($base) eq __PACKAGE__ or _valid(\$base); _any2ui($$base) // return $FALSE } : 10;

    __is_int__($n) || return $FALSE;
    return $FALSE if ($n <= 3);
    return $FALSE if ($base < 2);

    # Exclude primes and trivial small values
    if (FAST_MODE and !ref($n)) {
        return $FALSE if (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime($n) : Math::Prime::Util::GMP::is_prime($n));
    }
    else {
        return $FALSE if _is_prob_prime(_big2uistr($n) // return $FALSE);
    }

    my $digit_sum =
      (HAS_PRIME_UTIL and $n < ULONG_MAX and $base == 10)
      ? Math::Prime::Util::sumdigits($n)
      : List::Util::sum(Math::Prime::Util::GMP::todigits($n, $base));

    my $factor_digit_sum = 0;
    foreach my $pp (_factor_exp($n)) {
        my ($p, $e) = @$pp;
        my $p_sum =
          (HAS_PRIME_UTIL and $p < ULONG_MAX and $base == 10)
          ? Math::Prime::Util::sumdigits($p)
          : List::Util::sum(Math::Prime::Util::GMP::todigits($p, $base));
        $factor_digit_sum += $p_sum * $e;
        if ($factor_digit_sum > $digit_sum) {
            return $FALSE;
        }
    }

    ($factor_digit_sum == $digit_sum) ? $TRUE : $FALSE;
}

sub factorial_power {
    my ($n, $p) = @_;

    ref($p) eq __PACKAGE__ or _valid(\$p);

    $n = $$n;
    $p = $$p;

    # Generalized method for composite p
    # Formula from SymPy:
    #   https://github.com/sympy/sympy/blob/d7938cbc2f703801413075c147f54db350d29bff/sympy/ntheory/factor_.py#L227

    if (!_is_prob_prime($p)) {
        my %seen;
        my @list;
        foreach my $pp (grep { !$seen{$_->[1]}++ } CORE::reverse(_factor_exp($p))) {
            push @list,
              _set_int(
                       Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::subint($n, ${$_[0]->sumdigits(_set_int($pp->[0])) // return ZERO}),
                                                      Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::subint($pp->[0], 1), $pp->[1]))
                      );
        }
        return (_array(\@list)->imin() // ZERO);
    }

    # Formula for prime p:
    #   (n - sumdigits(n,p)) / (p-1)

    if (FAST_MODE and !ref($n) and !ref($p) and $p < 2147483647 and $n > 0 and $p > 1) {

        my $sum =
          HAS_PRIME_UTIL
          ? List::Util::sum(Math::Prime::Util::todigits($n, $p))
          : List::Util::sum(Math::Prime::Util::GMP::todigits($n, $p));

        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n - $sum, $p - 1)
          : Math::Prime::Util::GMP::divint($n - $sum, $p - 1);

        return bless \$r;
    }

    my $sum = ${$_[0]->sumdigits($_[1]) // return ZERO};
    bless \__div__(__sub__($n, $sum), __dec__($p));
}

*factorial_valuation = \&factorial_power;

sub length {
    my ($x, $y) = @_;

    $x = $$x;

    if (FAST_MODE and !defined($y) and !ref($x)) {
        my $len = CORE::length($x) - (($x < 0) ? 1 : 0);
        return bless \$len;
    }

    $x = _any2mpz($x) // return undef;

    my $neg = ((Math::GMPz::Rmpz_sgn($x) || return ONE) < 0) ? 1 : 0;

    $y = defined($y) ? do { _valid(\$y); _any2mpz($$y) // return undef } : 10;

    if ($neg) {
        $x = Math::GMPz::Rmpz_init_set($x);
        Math::GMPz::Rmpz_abs($x, $x);
    }

    _set_int(1 + (__ilog__($x, $y) // return ZERO));
}

*len  = \&length;
*size = \&length;

sub __floor__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return $x});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_floor($r, $x);
        return $r;
    }

  Math_GMPq: {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_q($r, $x);
        if (Math::GMPq::Rmpq_integer_p($x)) {
            return (
                      Math::GMPz::Rmpz_fits_slong_p($r)
                    ? Math::GMPz::Rmpz_get_si($r)
                    : Math::GMPz::Rmpz_init_set($r)
                   );
        }
        Math::GMPz::Rmpz_sub_ui($r, $r, 1) if Math::GMPq::Rmpq_sgn($x) < 0;
        return (
                  Math::GMPz::Rmpz_fits_slong_p($r)
                ? Math::GMPz::Rmpz_get_si($r)
                : Math::GMPz::Rmpz_init_set($r)
               );
    }

  Math_MPC: {
        my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($real, $x);
        Math::MPC::RMPC_IM($imag, $x);

        Math::MPFR::Rmpfr_floor($real, $real);
        Math::MPFR::Rmpfr_floor($imag, $imag);

        if (Math::MPFR::Rmpfr_zero_p($imag)) {
            return $real;
        }

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr_fr($r, $real, $imag, $ROUND);
        return $r;
    }
}

sub floor {
    my ($x) = @_;
    (!ref($$x) or ref($$x) eq 'Math::GMPz') and return $x;    # already an integer
    bless \__floor__($$x);
}

sub __ceil__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x) || return $x});

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_ceil($r, $x);
        return $r;
    }

  Math_GMPq: {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_q($r, $x);
        if (Math::GMPq::Rmpq_integer_p($x)) {
            return (
                      Math::GMPz::Rmpz_fits_slong_p($r)
                    ? Math::GMPz::Rmpz_get_si($r)
                    : Math::GMPz::Rmpz_init_set($r)
                   );
        }
        Math::GMPz::Rmpz_add_ui($r, $r, 1) if Math::GMPq::Rmpq_sgn($x) > 0;
        return (
                  Math::GMPz::Rmpz_fits_slong_p($r)
                ? Math::GMPz::Rmpz_get_si($r)
                : Math::GMPz::Rmpz_init_set($r)
               );
    }

  Math_MPC: {
        my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($real, $x);
        Math::MPC::RMPC_IM($imag, $x);

        Math::MPFR::Rmpfr_ceil($real, $real);
        Math::MPFR::Rmpfr_ceil($imag, $imag);

        if (Math::MPFR::Rmpfr_zero_p($imag)) {
            return $real;
        }

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr_fr($r, $real, $imag, $ROUND);
        return $r;
    }
}

sub ceil {
    my ($x) = @_;
    (!ref($$x) or ref($$x) eq 'Math::GMPz') and return $x;    # already an integer
    bless \__ceil__($$x);
}

*ceiling = \&ceil;

sub __inc__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        my $r = $x + 1;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_GMPz: {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_add_ui($r, $x, 1);
        return (
                  Math::GMPz::Rmpz_fits_slong_p($r)
                ? Math::GMPz::Rmpz_get_si($r)
                : Math::GMPz::Rmpz_init_set($r)
               );
    }

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_add_ui($r, $x, 1, $ROUND);
        return $r;
    }

  Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_add_z($r, $x, $ONE);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_add_ui($r, $x, 1, $ROUND);
        return $r;
    }
}

sub inc {
    my ($x) = @_;
    bless \__inc__($$x);
}

sub __dec__ {
    my ($x) = @_;
    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        my $r = $x - 1;
        if (FAST_MODE and $r < ULONG_MAX and $r > LONG_MIN) {
            return $r;
        }
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_GMPz: {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_sub_ui($r, $x, 1);
        return (
                  Math::GMPz::Rmpz_fits_slong_p($r)
                ? Math::GMPz::Rmpz_get_si($r)
                : Math::GMPz::Rmpz_init_set($r)
               );
    }

  Math_MPFR: {
        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_sub_ui($r, $x, 1, $ROUND);
        return $r;
    }

  Math_GMPq: {
        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_sub_z($r, $x, $ONE);
        return $r;
    }

  Math_MPC: {
        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_sub_ui($r, $x, 1, $ROUND);
        return $r;
    }
}

sub dec {
    my ($x) = @_;
    bless \__dec__($$x);
}

sub __mod__ {
    my ($x, $y) = @_;
    goto($DISPATCH_TAG{ref($x)} . '__' . $DISPATCH_TAG{ref($y)});

    #
    ## Scalar
    #
  Scalar__Scalar: {
        $y || goto &_nan;
        return $x % $y;
    }

  Scalar__Math_GMPz: {

        # y is a native integer
        if (Math::GMPz::Rmpz_fits_ulong_p($y)) {
            $y = Math::GMPz::Rmpz_get_ui($y) || goto &_nan;
            return $x % $y;
        }

        # x is positive and y > x
        if ($x > 0 and Math::GMPz::Rmpz_cmp_ui($y, $x) > 0) {
            return $x;
        }

        $x = _any2mpz($x);
        goto Math_GMPz__Math_GMPz;
    }

  Scalar__Math_GMPq: {
        $x = _any2mpz($x);
        goto Math_GMPz__Math_GMPq;
    }

  Scalar__Math_MPFR: {
        $x = _any2mpz($x);
        goto Math_GMPz__Math_MPFR;
    }

  Scalar__Math_MPC: {
        $x = _any2mpz($x);
        goto Math_GMPz__Math_MPC;
    }

    #
    ## GMPq
    #
  Math_GMPq__Math_GMPq: {

        if (Math::GMPq::Rmpq_integer_p($y)) {
            $y = _mpq2mpz($y);
            goto Math_GMPq__Math_GMPz;
        }

        Math::GMPq::Rmpq_sgn($y) || goto &_nan;

        my $quo = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_div($quo, $x, $y);

        # Floor
        Math::GMPq::Rmpq_integer_p($quo) || do {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_set_q($z, $quo);
            Math::GMPz::Rmpz_sub_ui($z, $z, 1) if Math::GMPq::Rmpq_sgn($quo) < 0;
            Math::GMPq::Rmpq_set_z($quo, $z);
        };

        Math::GMPq::Rmpq_mul($quo, $quo, $y);
        Math::GMPq::Rmpq_sub($quo, $x, $quo);

        return $quo;
    }

  Math_GMPq__Math_GMPz: {
        my $sgn_y = Math::GMPz::Rmpz_sgn($y) || goto &_nan;

        my $r = _modular_rational($x, $y) // do {

            my $quo = Math::GMPq::Rmpq_init();
            Math::GMPq::Rmpq_div_z($quo, $x, $y);

            # Floor
            Math::GMPq::Rmpq_integer_p($quo) || do {
                my $z = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_set_q($z, $quo);
                Math::GMPz::Rmpz_sub_ui($z, $z, 1) if Math::GMPq::Rmpq_sgn($quo) < 0;
                Math::GMPq::Rmpq_set_z($quo, $z);
            };

            Math::GMPq::Rmpq_mul_z($quo, $quo, $y);
            Math::GMPq::Rmpq_sub($quo, $x, $quo);

            return $quo;
        };

        Math::GMPz::Rmpz_mod($r, $r, $y);

        if (!Math::GMPz::Rmpz_sgn($r)) {
            ## ok
        }
        elsif ($sgn_y < 0) {
            Math::GMPz::Rmpz_add($r, $r, $y);
        }

        $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPq__Math_MPFR: {
        $x = _mpq2mpfr($x);
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPq__Math_MPC: {
        $x = _mpq2mpc($x);
        goto Math_MPC__Math_MPC;
    }

  Math_GMPq__Scalar: {
        $y = _any2mpz($y);
        goto Math_GMPq__Math_GMPz;
    }

    #
    ## GMPz
    #
  Math_GMPz__Math_GMPz: {

        if (Math::GMPz::Rmpz_fits_ulong_p($y)) {
            state $t = Math::GMPz::Rmpz_init_nobless();
            return Math::GMPz::Rmpz_mod_ui($t, $x, Math::GMPz::Rmpz_get_ui($y) || goto &_nan);
        }

        my $sgn_y = Math::GMPz::Rmpz_sgn($y) || goto &_nan;

        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mod($r, $x, $y);

        if (!Math::GMPz::Rmpz_sgn($r)) {
            ## ok
        }
        elsif ($sgn_y < 0) {
            Math::GMPz::Rmpz_add($r, $r, $y);
        }

        $r = Math::GMPz::Rmpz_get_si($r) if Math::GMPz::Rmpz_fits_slong_p($r);
        return $r;
    }

  Math_GMPz__Scalar: {

        if ($y < 0) {
            $y = _any2mpz($y);
            goto Math_GMPz__Math_GMPz;
        }

        $y || goto &_nan;

        state $t = Math::GMPz::Rmpz_init_nobless();
        return Math::GMPz::Rmpz_mod_ui($t, $x, $y);
    }

  Math_GMPz__Math_GMPq: {
        $x = _mpz2mpq($x);
        goto Math_GMPq__Math_GMPq;
    }

  Math_GMPz__Math_MPFR: {
        $x = _mpz2mpfr($x);
        goto Math_MPFR__Math_MPFR;
    }

  Math_GMPz__Math_MPC: {
        $x = _mpz2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## MPFR
    #
  Math_MPFR__Math_MPFR: {
        my $quo = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div($quo, $x, $y, $ROUND);
        Math::MPFR::Rmpfr_floor($quo, $quo);
        Math::MPFR::Rmpfr_mul($quo, $quo, $y, $ROUND);
        Math::MPFR::Rmpfr_sub($quo, $x, $quo, $ROUND);
        return $quo;
    }

  Math_MPFR__Scalar: {

        if ($y < 0) {
            $y = _any2mpz($y);
            goto Math_MPFR__Math_GMPz;
        }

        my $quo = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div_ui($quo, $x, $y, $ROUND);
        Math::MPFR::Rmpfr_floor($quo, $quo);
        Math::MPFR::Rmpfr_mul_ui($quo, $quo, $y, $ROUND);
        Math::MPFR::Rmpfr_sub($quo, $x, $quo, $ROUND);
        return $quo;
    }

  Math_MPFR__Math_GMPq: {
        my $quo = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div_q($quo, $x, $y, $ROUND);
        Math::MPFR::Rmpfr_floor($quo, $quo);
        Math::MPFR::Rmpfr_mul_q($quo, $quo, $y, $ROUND);
        Math::MPFR::Rmpfr_sub($quo, $x, $quo, $ROUND);
        return $quo;
    }

  Math_MPFR__Math_GMPz: {
        my $quo = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_div_z($quo, $x, $y, $ROUND);
        Math::MPFR::Rmpfr_floor($quo, $quo);
        Math::MPFR::Rmpfr_mul_z($quo, $quo, $y, $ROUND);
        Math::MPFR::Rmpfr_sub($quo, $x, $quo, $ROUND);
        return $quo;
    }

  Math_MPFR__Math_MPC: {
        $x = _mpfr2mpc($x);
        goto Math_MPC__Math_MPC;
    }

    #
    ## MPC
    #
  Math_MPC__Math_MPC: {
        my $quo = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_div($quo, $x, $y, $ROUND);

        my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($real, $quo);
        Math::MPC::RMPC_IM($imag, $quo);

        Math::MPFR::Rmpfr_floor($real, $real);
        Math::MPFR::Rmpfr_floor($imag, $imag);

        Math::MPC::Rmpc_set_fr_fr($quo, $real, $imag, $ROUND);

        Math::MPC::Rmpc_mul($quo, $quo, $y, $ROUND);
        Math::MPC::Rmpc_sub($quo, $x, $quo, $ROUND);

        return $quo;
    }

  Math_MPC__Scalar: {

        if ($y < 0) {
            $y = _any2mpz($y);
            goto Math_MPC__Math_GMPz;
        }

        my $quo = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_div_ui($quo, $x, $y, $ROUND);

        my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($real, $quo);
        Math::MPC::RMPC_IM($imag, $quo);

        Math::MPFR::Rmpfr_floor($real, $real);
        Math::MPFR::Rmpfr_floor($imag, $imag);

        Math::MPC::Rmpc_set_fr_fr($quo, $real, $imag, $ROUND);

        Math::MPC::Rmpc_mul_ui($quo, $quo, $y, $ROUND);
        Math::MPC::Rmpc_sub($quo, $x, $quo, $ROUND);

        return $quo;
    }

  Math_MPC__Math_MPFR: {
        $y = _mpfr2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_GMPz: {
        $y = _mpz2mpc($y);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_GMPq: {
        $y = _mpq2mpc($y);
        goto Math_MPC__Math_MPC;
    }
}

sub mod {
    my ($x, $y) = @_;

    my $ref = ref($y);
    if (exists $NUMBER_CLASSES{$ref}) {
        return $y->with_value($x)->mod($y);
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);
    bless \__mod__($$x, $$y);
}

sub smod {
    my ($x, $m) = @_;

    my $r = $x->mod($m);
    if ($r->add($r)->le($m)) {
        return $r;
    }

    return $r->sub($m);
}

sub polymod {
    my ($x, @m) = @_;

    _valid(map { \$_ } @m);

    $x = $$x;
    @m = map { $$_ } @m;

    my @r;
    foreach my $m (@m) {
        my $mod = __mod__($x, $m);

        $x = __sub__($x, $mod);
        $x = __div__($x, $m);

        push @r, $mod;
    }

    push @r, $x;
    map { bless \$_ } @r;
}

sub quadratic_nonresidue {
    my ($n) = @_;

    # Least quadratic non-residue of n. (OEIS: A020649)
    # Inspired by Dana Jacobsen's code from Math::Prime::Util::PP.

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_cmp_ui($n, 2) <= 0) {
        return _set_int($n);
    }

    if (Math::GMPz::Rmpz_ui_kronecker(2, $n) == -1) {
        return TWO;
    }

    if (_is_prob_prime($n)) {
        for (my $k = 3 ; ; $k = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($k) : Math::Prime::Util::GMP::next_prime($k))) {
            if (Math::GMPz::Rmpz_ui_kronecker($k, $n) == -1) {
                return _set_int($k);
            }
        }
    }

    if (Math::GMPz::Rmpz_even_p($n)) {
        return TWO if Math::GMPz::Rmpz_scan1($n, 0) >= 2;
    }

    foreach my $k (3, 5, 11, 13, 19) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $k)) {
            return TWO;
        }
    }

    my @factors = _factor_exp($n);

#<<<
    @factors = map {
        ($_ < ULONG_MAX)
            ? Math::GMPz::Rmpz_init_set_ui($_)
            : Math::GMPz::Rmpz_init_set_str("$_", 10)
    } map { $_->[0] } @factors;
#>>>

    for (my $k = 2 ; ; $k = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($k) : Math::Prime::Util::GMP::next_prime($k))) {
        foreach my $p (@factors) {
            if (Math::GMPz::Rmpz_cmp_ui($p, $k) > 0 and Math::GMPz::Rmpz_ui_kronecker($k, $p) == -1) {
                return _set_int($k);
            }
        }
    }
}

*qnr = \&quadratic_nonresidue;

sub sqrtmod {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (0 and HAS_PRIME_UTIL and !ref($y) and !ref($x)) {
        if (defined(my $r = Math::Prime::Util::sqrtmod($x, $y))) {
            return bless \$r;
        }
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;

    my $y_sgn = Math::GMPz::Rmpz_sgn($y) || goto &nan;

    if ($y_sgn < 0) {
        $y = Math::GMPz::Rmpz_init_set($y);
        Math::GMPz::Rmpz_abs($y, $y);
    }

    my $n = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($n, $x, $y);

    if (Math::GMPz::Rmpz_sgn($n) == 0) {
        return ZERO;
    }

    if (0 and HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($y)) {
        if (defined(my $r = Math::Prime::Util::sqrtmod(Math::GMPz::Rmpz_get_ui($n), Math::GMPz::Rmpz_get_ui($y)))) {
            return bless \$r;
        }
    }

    my $nstr = Math::GMPz::Rmpz_get_str($n, 10);
    my $ystr = Math::GMPz::Rmpz_get_str($y, 10);

    if (_is_prob_prime($ystr)) {
        return _set_int(Math::Prime::Util::GMP::sqrtmod($nstr, $ystr) // goto &nan);
    }

    if (0 and HAS_PRIME_UTIL) {
        if (defined(my $r = Math::Prime::Util::sqrtmod($nstr, $ystr))) {
            return _set_int("$r");
        }
    }

    # Fallback to rootmod(a,2,m)
    return _set_int($n)->rootmod(TWO, _set_int($y));
}

sub sqrtmod_all {
    my ($A, $N) = @_;
    return $A->rootmod_all(TWO, $N);
}

#----------------------------------------------------------
# Tonelli-Shanks algorithm for k-th roots modulo a prime
#----------------------------------------------------------
sub _tonelli_shanks {
    my ($A, $k, $p) = @_;

    my $exp = 0;
    my $q   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($q, $p, 1);

    while (Math::GMPz::Rmpz_divisible_p($q, $k)) {
        $exp++;
        Math::GMPz::Rmpz_divexact($q, $q, $k);
    }

    my $k_exp = Math::GMPz::Rmpz_init();
    my $tmp   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($tmp, $p, 1);
    Math::GMPz::Rmpz_divexact($k_exp, $tmp, $q);

    my $inv_k   = Math::GMPz::Rmpz_init();
    my $k_mod_q = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($k_mod_q, $k, $q);
    Math::GMPz::Rmpz_invert($inv_k, $k_mod_q, $q);

    my $root = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_powm($root, $A, $inv_k, $p);

    my $root_k = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_powm($root_k, $root, $k, $p);

    my $inv_a = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_invert($inv_a, $A, $p);

    my $B = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($B, $root_k, $inv_a);
    Math::GMPz::Rmpz_mod($B, $B, $p);

    # Find a generator of the k-th roots of unity
    my $candidate   = Math::GMPz::Rmpz_init_set_ui(2);
    my $zeta        = Math::GMPz::Rmpz_init_set_ui(1);
    my $gen         = Math::GMPz::Rmpz_init();
    my $k_exp_div_k = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_divexact($k_exp_div_k, $k_exp, $k);

    while (Math::GMPz::Rmpz_cmp_ui($zeta, 1) == 0) {
        Math::GMPz::Rmpz_powm($gen,  $candidate, $q,           $p);
        Math::GMPz::Rmpz_powm($zeta, $gen,       $k_exp_div_k, $p);
        Math::GMPz::Rmpz_add_ui($candidate, $candidate, 1);
    }

    # Iteratively refine the root
    my $new_gen           = Math::GMPz::Rmpz_init();
    my $k_exp_div_k_inner = Math::GMPz::Rmpz_init();
    my $test              = Math::GMPz::Rmpz_init();

    while (Math::GMPz::Rmpz_cmp($k_exp, $k) != 0) {
        Math::GMPz::Rmpz_divexact($k_exp, $k_exp, $k);

        Math::GMPz::Rmpz_powm($new_gen, $gen, $k, $p);
        Math::GMPz::Rmpz_set($candidate, $gen);
        Math::GMPz::Rmpz_set($gen,       $new_gen);

        Math::GMPz::Rmpz_divexact($k_exp_div_k_inner, $k_exp, $k);
        Math::GMPz::Rmpz_powm($test, $B, $k_exp_div_k_inner, $p);

        while (Math::GMPz::Rmpz_cmp_ui($test, 1) != 0) {
            Math::GMPz::Rmpz_mul($root, $root, $candidate);
            Math::GMPz::Rmpz_mod($root, $root, $p);

            Math::GMPz::Rmpz_mul($B, $B, $gen);
            Math::GMPz::Rmpz_mod($B, $B, $p);

            Math::GMPz::Rmpz_mul($test, $test, $zeta);
            Math::GMPz::Rmpz_mod($test, $test, $p);
        }
    }

    return ($root, $gen);    # return both root and zeta (gen)
}

#----------------------------------------------------------
# Chinese Remainder Theorem:   combine roots from two moduli
#----------------------------------------------------------
sub _crt_combine {
    my ($roots_a, $mod_a, $roots_b, $mod_b, $single_root) = @_;

    state $mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($mod, $mod_a, $mod_b);

    state $inv = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_invert($inv, $mod_a, $mod_b)
      or die "CRT: undefined inverse";

    my @roots;
    state $diff   = Math::GMPz::Rmpz_init();
    state $result = Math::GMPz::Rmpz_init();

    for my $ra (@$roots_a) {
        for my $rb (@$roots_b) {
            Math::GMPz::Rmpz_sub($diff, $rb, $ra);
            Math::GMPz::Rmpz_mul($diff, $diff, $inv);
            Math::GMPz::Rmpz_mod($diff, $diff, $mod_b);

            Math::GMPz::Rmpz_mul($result, $mod_a, $diff);
            Math::GMPz::Rmpz_add($result, $result, $ra);
            Math::GMPz::Rmpz_mod($result, $result, $mod);

            push @roots, Math::GMPz::Rmpz_init_set($result);
            return \@roots if $single_root;
        }
    }

    return \@roots;
}

#----------------------------------------------------------
# All k-th roots of a modulo prime p
#----------------------------------------------------------
sub _roots_mod_prime {
    my ($A, $k, $p, $single_root) = @_;

    state $a_mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($a_mod, $A, $p);

    if (Math::GMPz::Rmpz_cmp_ui($p, 2) == 0 || Math::GMPz::Rmpz_cmp_ui($a_mod, 0) == 0) {
        return [Math::GMPz::Rmpz_init_set($a_mod)];
    }

    state $phi = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($phi, $p, 1);

    state $g = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_gcd($g, $k, $phi);

    # Unique root when gcd(k, p-1) = 1
    if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
        my $k_mod_phi = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mod($k_mod_phi, $k, $phi);
        my $inv = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_invert($inv, $k_mod_phi, $phi);
        my $root = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_powm($root, $a_mod, $inv, $p);
        return [$root];
    }

    # No roots if a is not a k-th power residue
    state $phi_div_g = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_divexact($phi_div_g, $phi, $g);
    state $test = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_powm($test, $a_mod, $phi_div_g, $p);
    return [] if (Math::GMPz::Rmpz_cmp_ui($test, 1) != 0);

    if (Math::GMPz::Rmpz_cmp_ui($p, 3) == 0) {
        return [Math::GMPz::Rmpz_init_set_ui(1)] if $single_root;
        return [Math::GMPz::Rmpz_init_set_ui(1), Math::GMPz::Rmpz_init_set_ui(2)];
    }

    # Find one root and generate all others using roots of unity
    my ($root, $zeta) = _tonelli_shanks($a_mod, $k, $p);

    if (Math::GMPz::Rmpz_cmp_ui($zeta, 0) == 0) {
        die "Failed to find root";
    }
    state $root_k = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_powm($root_k, $root, $k, $p);
    if (Math::GMPz::Rmpz_cmp($root_k, $a_mod) != 0) {
        die "Failed to find root";
    }

    my @roots = (Math::GMPz::Rmpz_init_set($root));

    return \@roots if $single_root;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $root, $zeta);
    Math::GMPz::Rmpz_mod($r, $r, $p);

    my $k_ui = Math::GMPz::Rmpz_get_ui($k);

    while (Math::GMPz::Rmpz_cmp($r, $root) != 0 && scalar(@roots) < $k_ui) {
        push @roots, Math::GMPz::Rmpz_init_set($r);
        Math::GMPz::Rmpz_mul($r, $r, $zeta);
        Math::GMPz::Rmpz_mod($r, $r, $p);
    }

    return \@roots;
}

#----------------------------------------------------------
# Hensel lifting helpers
#----------------------------------------------------------
sub _hensel_lift_standard {
    my ($roots, $A, $k, $mod, $single_root) = @_;

    my @result;

    state $k_minus_1 = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($k_minus_1, $k, 1);

    state $s_pow     = Math::GMPz::Rmpz_init();
    state $deriv     = Math::GMPz::Rmpz_init();
    state $s_k       = Math::GMPz::Rmpz_init();
    state $residue   = Math::GMPz::Rmpz_init();
    state $common    = Math::GMPz::Rmpz_init();
    state $res_div   = Math::GMPz::Rmpz_init();
    state $deriv_div = Math::GMPz::Rmpz_init();
    state $inv_deriv = Math::GMPz::Rmpz_init();
    state $quot      = Math::GMPz::Rmpz_init();
    state $new_s     = Math::GMPz::Rmpz_init();

    for my $s (@$roots) {
        Math::GMPz::Rmpz_powm($s_pow, $s, $k_minus_1, $mod);

        Math::GMPz::Rmpz_mul($deriv, $k, $s_pow);
        Math::GMPz::Rmpz_mod($deriv, $deriv, $mod);

        Math::GMPz::Rmpz_powm($s_k, $s, $k, $mod);

        Math::GMPz::Rmpz_sub($residue, $A, $s_k);
        Math::GMPz::Rmpz_mod($residue, $residue, $mod);
        Math::GMPz::Rmpz_gcd($common, $residue, $deriv);

        Math::GMPz::Rmpz_divexact($res_div,   $residue, $common);
        Math::GMPz::Rmpz_divexact($deriv_div, $deriv,   $common);

        Math::GMPz::Rmpz_invert($inv_deriv, $deriv_div, $mod);

        Math::GMPz::Rmpz_mul($quot, $res_div, $inv_deriv);
        Math::GMPz::Rmpz_mod($quot, $quot, $mod);

        Math::GMPz::Rmpz_add($new_s, $s, $quot);
        Math::GMPz::Rmpz_mod($new_s, $new_s, $mod);

        push @result, Math::GMPz::Rmpz_init_set($new_s);
        last if $single_root;
    }
    return \@result;
}

sub _hensel_lift_singular {
    my ($roots, $A, $k, $p, $mod, $single_root) = @_;

    state $ext_mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($ext_mod, $mod, $p);

    state $submod_val = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_divexact($submod_val, $mod, $p);

    my %seen;

    state $k_minus_1 = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($k_minus_1, $k, 1);

    state $s_pow     = Math::GMPz::Rmpz_init();
    state $deriv     = Math::GMPz::Rmpz_init();
    state $s_k       = Math::GMPz::Rmpz_init();
    state $residue   = Math::GMPz::Rmpz_init();
    state $common    = Math::GMPz::Rmpz_init();
    state $res_div   = Math::GMPz::Rmpz_init();
    state $deriv_div = Math::GMPz::Rmpz_init();
    state $inv_deriv = Math::GMPz::Rmpz_init();
    state $quot      = Math::GMPz::Rmpz_init();
    state $r         = Math::GMPz::Rmpz_init();
    state $r_k       = Math::GMPz::Rmpz_init();
    state $A_mod     = Math::GMPz::Rmpz_init();
    state $i_val     = Math::GMPz::Rmpz_init();
    state $new_r     = Math::GMPz::Rmpz_init();

    my $k_ui = Math::GMPz::Rmpz_get_ui($k);

    for my $s (@$roots) {
        Math::GMPz::Rmpz_powm($s_pow, $s, $k_minus_1, $ext_mod);

        Math::GMPz::Rmpz_mul($deriv, $k, $s_pow);
        Math::GMPz::Rmpz_mod($deriv, $deriv, $ext_mod);
        Math::GMPz::Rmpz_powm($s_k, $s, $k, $ext_mod);

        Math::GMPz::Rmpz_sub($residue, $A, $s_k);
        Math::GMPz::Rmpz_mod($residue, $residue, $ext_mod);
        Math::GMPz::Rmpz_gcd($common, $residue, $deriv);

        Math::GMPz::Rmpz_divexact($res_div,   $residue, $common);
        Math::GMPz::Rmpz_divexact($deriv_div, $deriv,   $common);

        Math::GMPz::Rmpz_invert($inv_deriv, $deriv_div, $mod);

        Math::GMPz::Rmpz_mul($quot, $res_div, $inv_deriv);
        Math::GMPz::Rmpz_mod($quot, $quot, $mod);

        Math::GMPz::Rmpz_add($r, $s, $quot);
        Math::GMPz::Rmpz_mod($r, $r, $mod);

        Math::GMPz::Rmpz_powm($r_k, $r, $k, $mod);

        Math::GMPz::Rmpz_mod($A_mod, $A, $mod);
        next if (Math::GMPz::Rmpz_cmp($r_k, $A_mod) != 0);

        for my $i (0 .. $k_ui - 1) {
            Math::GMPz::Rmpz_mul_ui($i_val, $submod_val, $i);
            Math::GMPz::Rmpz_mod($i_val, $i_val, $mod);
            Math::GMPz::Rmpz_add_ui($i_val, $i_val, 1);
            Math::GMPz::Rmpz_mod($i_val, $i_val, $mod);

            Math::GMPz::Rmpz_mul($new_r, $r, $i_val);
            Math::GMPz::Rmpz_mod($new_r, $new_r, $mod);

            $seen{Math::GMPz::Rmpz_get_str($new_r, 10)} = Math::GMPz::Rmpz_init_set($new_r);
            return [values %seen] if $single_root;
        }
    }
    return [values %seen];
}

#----------------------------------------------------------
# All k-th roots of r modulo prime power p^e
#----------------------------------------------------------
sub _roots_mod_prime_power {
    my ($r, $k, $p, $e, $single_root) = @_;

    return _roots_mod_prime($r, $k, $p, $single_root) if ($e == 1);

    my $mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_pow_ui($mod, $p, $e);

    my $k_ui = Math::GMPz::Rmpz_get_ui($k);
    my $pk   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_pow_ui($pk, $p, $k_ui);

    # Special case:   a ≡ 0 (mod p^e)
    my $r_mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($r_mod, $r, $mod);
    if (Math::GMPz::Rmpz_cmp_ui($r_mod, 0) == 0) {
        my $t  = CORE::int(($e - 1) / $k_ui) + 1;
        my $pt = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($pt, $p, $t);
        my $cnt = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($cnt, $p, $e - $t);
        my $cnt_ui = Math::GMPz::Rmpz_get_ui($cnt);

        my @result;
        my $val = Math::GMPz::Rmpz_init();
        for my $i (0 .. $cnt_ui - 1) {
            Math::GMPz::Rmpz_mul_ui($val, $pt, $i);
            Math::GMPz::Rmpz_mod($val, $val, $mod);
            push @result, Math::GMPz::Rmpz_init_set($val);
            last if $single_root;
        }
        return \@result;
    }

    # Special case:  a ≡ 0 (mod p^k) but a ≢ 0 (mod p^e)
    my $r_mod_pk = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($r_mod_pk, $r, $pk);
    if (Math::GMPz::Rmpz_cmp_ui($r_mod_pk, 0) == 0) {

        my $factor = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($factor, $p, ($e - $k_ui) + 1);

        my $count = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($count, $p, $k_ui - 1);

        my $count_ui = Math::GMPz::Rmpz_get_ui($count);
        my $r_div_pk = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_divexact($r_div_pk, $r, $pk);

        my $sub = _roots_mod_prime_power($r_div_pk, $k, $p, $e - $k_ui, $single_root);

        return [] if !@$sub;

        my @result;
        my $base = Math::GMPz::Rmpz_init();
        my $val  = Math::GMPz::Rmpz_init();

        for my $s (@$sub) {
            Math::GMPz::Rmpz_mul($base, $s, $p);
            Math::GMPz::Rmpz_mod($base, $base, $mod);

            for my $i (0 .. $count_ui - 1) {
                Math::GMPz::Rmpz_mul_ui($val, $factor, $i);
                Math::GMPz::Rmpz_add($val, $val, $base);
                Math::GMPz::Rmpz_mod($val, $val, $mod);
                push @result, Math::GMPz::Rmpz_init_set($val);
                return \@result if $single_root;
            }
        }
        return \@result;
    }

    # No roots if p | a but p^k ∤ a
    my $r_mod_p = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mod($r_mod_p, $r, $p);
    return [] if (Math::GMPz::Rmpz_cmp_ui($r_mod_p, 0) == 0);

    # Hensel lifting from smaller exponent
    my $half =
      (Math::GMPz::Rmpz_cmp_ui($p, 2) > 0 || $e < 5)
      ? CORE::int(($e + 1) / 2)
      : CORE::int(($e + 3) / 2);

    my $sub = _roots_mod_prime_power($r, $k, $p, $half, $single_root);

    if (Math::GMPz::Rmpz_cmp($k, $p) != 0) {
        return _hensel_lift_standard($sub, $r, $k, $mod, $single_root);
    }
    else {
        return _hensel_lift_singular($sub, $r, $k, $p, $mod, $single_root);
    }
}

#----------------------------------------------------------
# All k-th roots of r modulo n (with factorization)
#----------------------------------------------------------
sub _roots_mod_composite {
    my ($r, $k, $factors, $single_root) = @_;

    my $mod   = Math::GMPz::Rmpz_init_set_ui(1);
    my $roots = [];
    my $pe    = Math::GMPz::Rmpz_init();

    for my $factor (@$factors) {
        my ($p, $e) = @$factor;

        my $sub = _roots_mod_prime_power($r, $k, $p, $e, $single_root);
        return [] if (!@$sub);

        Math::GMPz::Rmpz_pow_ui($pe, $p, $e);

        if (@$roots) {
            $roots = _crt_combine($roots, $mod, $sub, $pe, $single_root);
        }
        else {
            $roots = $sub;
        }
        Math::GMPz::Rmpz_mul($mod, $mod, $pe);
    }
    return $roots;
}

#----------------------------------------------------------
# Main entry point:   all k-th roots of A modulo n
#----------------------------------------------------------
sub rootmod_all {
    my ($A, $k, $n, $single_root) = @_;

    $A = _any2mpz($$A) // return _array();
    $k = _any2mpz($$k) // return _array();
    $n = _any2mpz($$n) // return _array();

    if (HAS_PRIME_UTIL and !$single_root and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $A = Math::GMPz::Rmpz_get_str($A, 10);
        $k = Math::GMPz::Rmpz_get_str($k, 10);
        $n = Math::GMPz::Rmpz_get_ui($n);
        my @roots = Math::Prime::Util::allrootmod($A, $k, $n);
        return _array([map { bless \$_ } @roots]);
    }

    # return _array([map {_set_int($_)} Math::Prime::Util::GMP::allrootmod($$A, $$k, $$n)]);

    $A = Math::GMPz::Rmpz_init_set($A);    # copy
    $k = Math::GMPz::Rmpz_init_set($k);    # copy
    $n = Math::GMPz::Rmpz_init_set($n);    # copy

    Math::GMPz::Rmpz_abs($n, $n);
    return _array() if (Math::GMPz::Rmpz_cmp_ui($n, 0) == 0);

    Math::GMPz::Rmpz_mod($A, $A, $n);

    if (Math::GMPz::Rmpz_cmp_ui($k, 0) <= 0 && Math::GMPz::Rmpz_cmp_ui($A, 0) == 0) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($k) < 0) {
        my $inv = Math::GMPz::Rmpz_init();
        if (!Math::GMPz::Rmpz_invert($inv, $A, $n)) {
            return _array();
        }
        my $g = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_gcd($g, $inv, $n);
        return _array() if (Math::GMPz::Rmpz_cmp_ui($g, 1) != 0);
        Math::GMPz::Rmpz_set($A, $inv);
        Math::GMPz::Rmpz_neg($k, $k);
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, 2) <= 0 || Math::GMPz::Rmpz_cmp_ui($k, 1) == 0) {
        return _array([_set_int($A)]);
    }

    if (Math::GMPz::Rmpz_cmp_ui($k, 0) == 0) {
        if (Math::GMPz::Rmpz_cmp_ui($A, 1) == 0) {
            return _array([ZERO]) if $single_root;
            my $n_ui = Math::GMPz::Rmpz_get_ui($n);
            return _array([map { bless \$_ } (0 .. $n_ui - 1)]);
        }
        return _array();
    }

    my @factors = map { [Math::GMPz::Rmpz_init_set_str($_->[0], 10), $_->[1]] } _factor_exp($n);

    my $roots     = [Math::GMPz::Rmpz_init_set($A)];
    my @k_factors = map { Math::GMPz::Rmpz_init_set_str($_, 10) } _factor($k);

    for my $prime_factor (@k_factors) {
        my @new_roots;
        for my $r (@$roots) {
            my $sub = _roots_mod_composite($r, $prime_factor, \@factors, $single_root);
            push @new_roots, @$sub;
        }
        $roots = \@new_roots;
    }

    _array([map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } @$roots]);
}

sub rootmod {
    my ($A, $k, $n) = @_;
    $A->rootmod_all($k, $n, 1)->first // goto &nan;
}

# Modular roots for a list of polynomial coefficients

sub _dedupe_sort_gmpz {
    my (@list) = @_;

    return ()    if scalar(@list) == 0;
    return @list if scalar(@list) == 1;

    @list = sort { Math::GMPz::Rmpz_cmp($a, $b) } @list;

    my @deduped = ($list[0]);
    for my $i (1 .. $#list) {
        push(@deduped, $list[$i]) if Math::GMPz::Rmpz_cmp($list[$i - 1], $list[$i]) != 0;
    }

    return @deduped;
}

sub _poly_eval_mod_dense {
    my ($x, $coeffs, $m, $out) = @_;

    $out //= Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_set_ui($out, 0);

    for (my $i = $#$coeffs ; $i >= 0 ; --$i) {
        Math::GMPz::Rmpz_mul($out, $out, $x);
        Math::GMPz::Rmpz_add($out, $out, $coeffs->[$i]);
        Math::GMPz::Rmpz_mod($out, $out, $m);
    }

    return $out;
}

sub _poly_derivative_dense {
    my ($coeffs) = @_;

    return [] if @$coeffs <= 1;

    my @d;
    for my $i (1 .. $#$coeffs) {
        my $c = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul_ui($c, $coeffs->[$i], $i);
        push @d, $c;
    }

    return \@d;
}

sub _poly_bruteforce_roots_mod {
    my ($coeffs, $m) = @_;

    # Fast method, based on polynomial factorization modulo p
    if (Math::GMPz::Rmpz_cmp_ui($m, 1e3) > 0 and _is_prob_prime($m)) {
        my @solutions;
        my $m_obj = bless \$m;
        my $poly  = Sidef::Types::Number::PolynomialMod->new([CORE::reverse(@$coeffs)], $m_obj);
        foreach my $f (map { $_->[0] } @{$poly->factor_exp}) {
            ref($f) eq 'Sidef::Types::Number::PolynomialMod' or next;
            $f->degree->is_one                               or next;
            push @solutions, $m_obj->sub($f->coeff(ZERO))->mod($m_obj);
        }
        if (@solutions) {
            return [map { _any2mpz($$_) } @solutions];
        }
    }

    state $x = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    return [] if !Math::GMPz::Rmpz_fits_ulong_p($m);

    my $mu = Math::GMPz::Rmpz_get_ui($m);
    return [] if $mu == 0;

    my @roots;

    for (my $i = 0 ; $i < $mu ; ++$i) {
        Math::GMPz::Rmpz_set_ui($x, $i);
        _poly_eval_mod_dense($x, $coeffs, $m, $v);
        push(@roots, Math::GMPz::Rmpz_init_set_ui($i)) if Math::GMPz::Rmpz_sgn($v) == 0;
    }

    return \@roots;
}

sub _poly_try_monomial {
    my ($coeffs, $mod) = @_;

    my @nz = grep { Math::GMPz::Rmpz_sgn($coeffs->[$_]) != 0 } 0 .. $#$coeffs;

    return []    if !@nz;                        # zero polynomial: unreachable after gcd-stripping, kept for safety
    return undef if @nz > 2;                     # too many terms for this shortcut
    return undef if @nz == 2 and $nz[0] != 0;    # binomial must have its low term at the constant

    my $k = $nz[-1];
    return [] if $k == 0;                        # single nonzero constant term: no roots, ever

    my $b0 = (@nz == 2) ? $coeffs->[0] : $ZERO;
    my $ak = $coeffs->[$k];

    my $inv = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_invert($inv, $ak, $mod) or return undef;

    my $A = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_neg($A, $b0);
    Math::GMPz::Rmpz_mul($A, $A, $inv);
    Math::GMPz::Rmpz_mod($A, $A, $mod);

    return [map { _any2mpz($$_) } @{_set_int($A)->rootmod_all(_set_int($k), _set_int($mod))}];
}

sub _reduce_coeffs_mod {
    my ($coeffs, $n) = @_;

    my @c = map { _any2mpz(__mod__($_, $n)) } @$coeffs;

    # Remove leading zero coefficients
    while (@c and Math::GMPz::Rmpz_sgn($c[-1]) == 0) {
        pop(@c);
    }

    return \@c;
}

sub _poly_solve_special_cases {
    my ($coeffs, $n) = @_;

    FAST_MODE || return undef;

    # Degree 0: a nonzero constant has no roots
    return [] if $#$coeffs == 0;

    # Degree 1: linear_congruence() handles this in full generality
    if ($#$coeffs == 1) {
        return solve_lcg_all(_set_int($coeffs->[1]), _set_int(__neg__($coeffs->[0])), _set_int($n));
    }

    # Sparse monomial/binomial (a*x^k + b == 0) with an invertible leading term.
    if (my $sols = _poly_try_monomial($coeffs, $n)) {
        return $sols;
    }

    # Degree 2: exact closed form, already handles composite moduli.
    if ($#$coeffs == 2) {
        return [grep { $_->is_int } @{solve_quadratic_mod(_set_int($coeffs->[2]), _set_int($coeffs->[1]), _set_int($coeffs->[0]), _set_int($n))}];
    }

    return undef;    # no special case detected
}

sub _poly_roots_mod_prime_power {
    my ($coeffs, $p, $e) = @_;

    my $mod = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_pow_ui($mod, $p, $e);

    $coeffs = _reduce_coeffs_mod($coeffs, $mod);

    if (defined(my $sol = _poly_solve_special_cases($coeffs, $mod))) {
        return $sol;
    }

    my $roots_p = _poly_bruteforce_roots_mod($coeffs, $p);
    return []       if !@$roots_p;
    return $roots_p if $e == 1;      # already the final modulus

    # Hensel lifting (level by level, handling branches)
    my @current_roots = @$roots_p;
    my $der           = _poly_derivative_dense($coeffs);
    my $pe            = Math::GMPz::Rmpz_init_set($p);

    state $mod_next = Math::GMPz::Rmpz_init_nobless();
    state $fx       = Math::GMPz::Rmpz_init_nobless();
    state $df       = Math::GMPz::Rmpz_init_nobless();
    state $q        = Math::GMPz::Rmpz_init_nobless();
    state $inv      = Math::GMPz::Rmpz_init_nobless();
    state $step     = Math::GMPz::Rmpz_init_nobless();

    for (my $j = 1 ; $j < $e ; ++$j) {
        Math::GMPz::Rmpz_mul($mod_next, $pe, $p);
        my @next_roots;

        for my $r (@current_roots) {
            _poly_eval_mod_dense($r, $coeffs, $mod_next, $fx);
            _poly_eval_mod_dense($r, $der,    $p,        $df);

            if (Math::GMPz::Rmpz_sgn($df) == 0) {

                # Case 1: f'(r) ≡ 0 (mod p)
                if (Math::GMPz::Rmpz_sgn($fx) == 0) {

                    # f(r) ≡ 0 (mod p^{j+1}) -> branches into p roots
                    my $p_int = Math::GMPz::Rmpz_get_ui($p);
                    for my $k (0 .. $p_int - 1) {
                        my $nr = Math::GMPz::Rmpz_init();
                        Math::GMPz::Rmpz_mul_ui($nr, $pe, $k);
                        Math::GMPz::Rmpz_add($nr, $nr, $r);
                        push @next_roots, $nr;
                    }
                }

                # Else: f(r) ≢ 0 (mod p^{j+1}) -> 0 roots (lift dies)
            }
            else {
                # Case 2: f'(r) ≢ 0 (mod p) -> 1 unique lift
                Math::GMPz::Rmpz_invert($inv, $df, $p);
                Math::GMPz::Rmpz_divexact($q, $fx, $pe);
                Math::GMPz::Rmpz_mul($step, $q, $inv);
                Math::GMPz::Rmpz_neg($step, $step);
                Math::GMPz::Rmpz_mod($step, $step, $p);

                my $nr = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($nr, $step, $pe);
                Math::GMPz::Rmpz_add($nr, $nr, $r);
                push @next_roots, $nr;
            }
        }

        @current_roots = @next_roots;
        last if !@current_roots;
        Math::GMPz::Rmpz_set($pe, $mod_next);
    }

    return [_dedupe_sort_gmpz(@current_roots)];
}

sub _solve_polynomial_congruence_all {
    my ($n, $coeffs) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _any2mpz($$n) // return [];
    Math::GMPz::Rmpz_sgn($n) > 0 or return [];

    $coeffs = [map { ref($_) eq __PACKAGE__ or _valid(\$_); _any2mpz($$_) } @$coeffs];
    $coeffs = _reduce_coeffs_mod($coeffs, $n);

    # Zero polynomial: every residue class is a solution.
    return _set_int($n)->range if !@$coeffs;

    # Remove a common factor from all coefficients and the modulus:
    # f(x) == 0 (mod n) is exactly equivalent to (f(x)/g) == 0 (mod n/g)
    # for g = gcd(n, all coefficients), and each solution mod n/g lifts to
    # exactly g residues mod n (base, base+n/g, base+2n/g, ...).
    my $common = Math::GMPz::Rmpz_init_set($n);
    foreach my $c (@$coeffs) {
        Math::GMPz::Rmpz_gcd($common, $common, $c) if Math::GMPz::Rmpz_sgn($c) != 0;
    }

    if (Math::GMPz::Rmpz_cmp_ui($common, 1) > 0) {
        my $n1 = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_divexact($n1, $n, $common);

        my @c1 = map {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_divexact($t, $_, $common);
            bless \$t;
        } @$coeffs;

        my $sub = __SUB__->(_set_int($n1), \@c1);
        return [] if !@$sub;

        Math::GMPz::Rmpz_fits_ulong_p($common) or return [];
        my $g = Math::GMPz::Rmpz_get_ui($common);

        my @lifted;
        foreach my $r (@$sub) {
            my $base = _any2mpz($r) // next;
            for my $t (0 .. $g - 1) {
                my $x = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul_ui($x, $n1, $t);
                Math::GMPz::Rmpz_add($x, $x, $base);
                Math::GMPz::Rmpz_mod($x, $x, $n);
                push @lifted, $x;
            }
        }

        return [_dedupe_sort_gmpz(@lifted)];
    }

    if (defined(my $sol = _poly_solve_special_cases($coeffs, $n))) {
        return $sol;
    }

    # General case: factor the modulus, solve modulo each prime power
    # independently, and recombine the root sets with CRT.
    my @factors = map { [_any2mpz($_->[0]), $_->[1]] } _factor_exp($n);
    return [] if !@factors;

    my ($roots, $mod);

    foreach my $f (@factors) {
        my ($p, $e) = @$f;

        my $pe = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($pe, $p, $e);

        my $sub = [map { _any2mpz($_) } @{_poly_roots_mod_prime_power($coeffs, $p, $e)}];
        return [] if !@$sub;

        if (!defined($roots)) {
            ($roots, $mod) = ($sub, $pe);
            next;
        }

        $roots = _crt_combine($roots, $mod, $sub, $pe, 0);
        Math::GMPz::Rmpz_mul($mod, $mod, $pe);

        return [] if !@$roots;
    }

    [_dedupe_sort_gmpz(map { _any2mpz($_) } @$roots)];
}

sub difference_of_squares {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();

    # No solutions when n == 2 (mod 4). See: A016825.
    if (Math::GMPz::Rmpz_congruent_ui_p($n, 2, 4)) {
        return _array();
    }

    my $D = (bless \$n)->divisors($_[0]->isqrt);

    my $t = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init();

    my @solutions;

    foreach my $d (@$D) {

        if (ref($$d)) {
            Math::GMPz::Rmpz_divexact($t, $n, $$d);
            Math::GMPz::Rmpz_add($u, $t, $$d);
        }
        else {
            Math::GMPz::Rmpz_divexact_ui($t, $n, $$d);
            Math::GMPz::Rmpz_add_ui($u, $t, $$d);
        }

        Math::GMPz::Rmpz_even_p($u) || next;

        my $x = Math::GMPz::Rmpz_init();
        my $y = Math::GMPz::Rmpz_init();

        ref($$d)
          ? Math::GMPz::Rmpz_sub($y, $t, $$d)
          : Math::GMPz::Rmpz_sub_ui($y, $t, $$d);

        Math::GMPz::Rmpz_div_2exp($x, $u, 1);
        Math::GMPz::Rmpz_div_2exp($y, $y, 1);

        unshift @solutions, _array([(bless \$x), (bless \$y)]);
    }

    _array(\@solutions);
}

*diff_of_squares = \&difference_of_squares;

sub difference_of_cubes {
    my ($n_in) = @_;
    my $n = _sanitize_mpz($$n_in) // return _array();

    my $sgn_n = Math::GMPz::Rmpz_sgn($n);
    $sgn_n == 0 and return _array();

    # Calculate divisors based strictly on absolute value
    my $abs_n = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_abs($abs_n, $n);

    my @solutions;

    # Reuse temporary scratchpad mpz objects across loop iterations
    state $A      = Math::GMPz::Rmpz_init_nobless();
    state $B      = Math::GMPz::Rmpz_init_nobless();
    state $A2     = Math::GMPz::Rmpz_init_nobless();
    state $D      = Math::GMPz::Rmpz_init_nobless();
    state $C      = Math::GMPz::Rmpz_init_nobless();
    state $Delta  = Math::GMPz::Rmpz_init_nobless();
    state $sqrt_D = Math::GMPz::Rmpz_init_nobless();
    state $num1   = Math::GMPz::Rmpz_init_nobless();
    state $num2   = Math::GMPz::Rmpz_init_nobless();

    foreach my $A_str (_divisors($abs_n)) {
        Math::GMPz::Rmpz_set_str($A, $A_str, 10);

        # A must carry the exact sign of n since B is strictly positive
        if ($sgn_n < 0) {
            Math::GMPz::Rmpz_neg($A, $A);
        }

        Math::GMPz::Rmpz_divexact($B, $n, $A);
        Math::GMPz::Rmpz_mul($A2, $A, $A);

        # For differences: D = 3xy = B - A^2
        Math::GMPz::Rmpz_sub($D, $B, $A2);

        if (Math::GMPz::Rmpz_divisible_ui_p($D, 3)) {
            Math::GMPz::Rmpz_divexact_ui($C, $D, 3);

            # Delta = (x+y)^2 = A^2 + 4*C
            Math::GMPz::Rmpz_mul_ui($Delta, $C, 4);
            Math::GMPz::Rmpz_add($Delta, $A2, $Delta);

            if (Math::GMPz::Rmpz_sgn($Delta) >= 0 && Math::GMPz::Rmpz_perfect_square_p($Delta)) {
                Math::GMPz::Rmpz_sqrt($sqrt_D, $Delta);

                # 2x = A + sqrt(Delta)
                Math::GMPz::Rmpz_add($num1, $A, $sqrt_D);

                # 2y = sqrt(Delta) - A
                Math::GMPz::Rmpz_sub($num2, $sqrt_D, $A);

                if (Math::GMPz::Rmpz_even_p($num1) && Math::GMPz::Rmpz_even_p($num2)) {
                    Math::GMPz::Rmpz_tdiv_q_2exp($num1, $num1, 1);
                    Math::GMPz::Rmpz_tdiv_q_2exp($num2, $num2, 1);

                    # First solution pair (x, y)
                    my $x1 = Math::GMPz->new();
                    my $y1 = Math::GMPz->new();
                    Math::GMPz::Rmpz_set($x1, $num1);
                    Math::GMPz::Rmpz_set($y1, $num2);

                    push @solutions, _array([bless(\$x1), bless(\$y1)]);

                    # Because x^3 - y^3 = (-y)^3 - (-x)^3 = n, we get a second pair if sqrt(Delta) > 0
                    if (Math::GMPz::Rmpz_sgn($sqrt_D) > 0) {
                        my $x2 = Math::GMPz->new();
                        my $y2 = Math::GMPz->new();

                        # x2 = -y1
                        Math::GMPz::Rmpz_neg($x2, $y1);

                        # y2 = -x1
                        Math::GMPz::Rmpz_neg($y2, $x1);

                        push @solutions, _array([bless(\$x2), bless(\$y2)]);
                    }
                }
            }
        }
    }

    @solutions = sort { ${$a->[0]} <=> ${$b->[0]} } @solutions;

    return _array(\@solutions);
}

*diff_of_cubes = \&difference_of_cubes;

sub _primitive_sum_of_two_squares {
    my ($p) = @_;

    # Trivial case: 2 = 1² + 1²
    if (Math::GMPz::Rmpz_cmp_ui($p, 2) == 0) {
        return (1, 1);
    }

    state %sos_cache;
    undef %sos_cache if scalar(keys(%sos_cache)) > 1e5;

    my $key = Math::GMPz::Rmpz_get_str($p, 16);

    if (exists $sos_cache{$key}) {
        return @{$sos_cache{$key}};
    }

    # Step 1: s ← sqrt(-1) mod p
    my $s = Math::GMPz::Rmpz_init_set_str((Math::Prime::Util::GMP::sqrtmod(-1, Math::GMPz::Rmpz_get_str($p, 10)) // die "error"), 10);
    my $q = Math::GMPz::Rmpz_init_set($p);

    state $t = Math::GMPz::Rmpz_init_nobless();

    # Step 2: Euclidean reduction — stop when s² ≤ p
    while (1) {
        Math::GMPz::Rmpz_mul($t, $s, $s);             # t = s²
        last if Math::GMPz::Rmpz_cmp($t, $p) <= 0;    # s² ≤ p → done

        # One Euclidean step: (q, s) ← (s, q mod s)
        Math::GMPz::Rmpz_set($t, $s);
        Math::GMPz::Rmpz_mod($s, $q, $s);
        Math::GMPz::Rmpz_set($q, $t);
    }

    # Step 3: b = (previous remainder) mod a
    Math::GMPz::Rmpz_mod($q, $q, $s);

    $sos_cache{$key} = [$q, $s];
    return ($q, $s);
}

sub _combine_pairs {
    my ($A,  $B,  $C,  $D)  = @_;
    my ($AC, $BD, $AD, $BC) = ($A * $C, $B * $D, $A * $D, $B * $C);
    return ([$AC - $BD, $AD + $BC], [$AC + $BD, $AD - $BC]);
}

sub _multiply_sets {
    my ($A, $B) = @_;
    my (@result, %seen);

    for my $p (@$A) {
        for my $q (@$B) {
            for my $r (_combine_pairs(@$p, @$q)) {
                my ($x, $y) = @$r;

                # Canonicalise: ensure non-negative and x ≤ y
                $x = -$x if $x < 0;
                $y = -$y if $y < 0;

                ($x, $y) = ($y, $x) if $x > $y;

                if (!$seen{$x}{$y}++) {
                    push @result, [$x, $y];
                }
            }
        }
    }

    return \@result;
}

sub sum_of_squares {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) >= 0 or return _array();

    if (Math::GMPz::Rmpz_sgn($n) == 0) {
        return _array(_array([ZERO, ZERO]));
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
        return _array(_array([ZERO, ONE]));
    }

    my @factor_exp = _factor_exp($n);

    # Feasibility check (Legendre's three-square theorem)
    # A prime factor p ≡ 3 (mod 4) must appear to an even power;
    # if any such prime appears to an odd power, no representation exists.
    for my $pp (@factor_exp) {
        my ($p, $k) = @$pp;
        next if ($k % 2 == 0);
        my $mod4 = ($p < ULONG_MAX) ? ($p % 4) : Math::Prime::Util::GMP::modint($p, 4);
        return _array() if ($mod4 == 3);
    }

    # Build representations prime-by-prime
    # Start from the trivial identity 1 = 0² + 1², then fold in each factor.
    my $reps         = [[0, 1]];
    my $square_scale = Math::GMPz::Rmpz_init_set_ui(1);

    state $p = Math::GMPz::Rmpz_init_nobless();

    for my $pp (@factor_exp) {
        my ($p_raw, $k) = @$pp;

        # Convert p to GMPz for all subsequent arithmetic
        (FAST_MODE and $p_raw < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $p_raw)
          : Math::GMPz::Rmpz_set_str($p, "$p_raw", 10);

        # Primes p ≡ 3 (mod 4) appear only to even powers (guaranteed above).
        # p^(2t) = (p^t)² is a perfect square; it scales both coordinates
        # uniformly by p^(k/2) without changing the number of representations.
        if (Math::GMPz::Rmpz_congruent_ui_p($p, 3, 4)) {
            Math::GMPz::Rmpz_pow_ui($p, $p, $k >> 1);    # p = p^(k/2)
            Math::GMPz::Rmpz_mul($square_scale, $square_scale, $p);
            next;
        }

        # p = 2 or p ≡ 1 (mod 4): obtain the primitive representation p = a² + b²
        my ($x, $y) = _primitive_sum_of_two_squares($p);

        # Lift p → p^k via binary exponentiation over representation sets.
        # @acc accumulates the running product starting from 1 = 0² + 1²;
        # @base holds the current power-of-two of p's representation.
        my $acc  = [[0, 1]];
        my $base = [[$x, $y]];
        my $exp  = $k;

        while ($exp > 0) {
            $acc = _multiply_sets($acc, $base) if $exp & 1;
            $exp >>= 1;
            $base = _multiply_sets($base, $base) if $exp > 0;    # skip final squaring
        }

        $reps = _multiply_sets($reps, $acc);
    }

    # Apply the p ≡ 3 (mod 4) scale factor
    if (Math::GMPz::Rmpz_cmp_ui($square_scale, 1) > 0) {
        @$reps = map { [$_->[0] * $square_scale, $_->[1] * $square_scale] } @$reps;
    }

    # Sort into canonical order (x ascending) and wrap for return
    @$reps = sort { $a->[0] <=> $b->[0] } @$reps;

    _array(
        [
         map {
             _array([map { _set_int($_) } @$_])
           } @$reps
        ]
    );
}

sub sum_of_cubes {
    my ($n_in) = @_;
    my $n = _sanitize_mpz($$n_in) // return _array();

    my $sgn_n = Math::GMPz::Rmpz_sgn($n);
    $sgn_n == 0 and return _array();

    # Calculate divisors based strictly on absolute value
    my $abs_n = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_abs($abs_n, $n);

    my @solutions;

    # Reuse temporary scratchpad mpz objects across loop iterations
    state $A      = Math::GMPz::Rmpz_init_nobless();
    state $B      = Math::GMPz::Rmpz_init_nobless();
    state $A2     = Math::GMPz::Rmpz_init_nobless();
    state $D      = Math::GMPz::Rmpz_init_nobless();
    state $C      = Math::GMPz::Rmpz_init_nobless();
    state $Delta  = Math::GMPz::Rmpz_init_nobless();
    state $sqrt_D = Math::GMPz::Rmpz_init_nobless();
    state $num1   = Math::GMPz::Rmpz_init_nobless();
    state $num2   = Math::GMPz::Rmpz_init_nobless();

    foreach my $A_str (_divisors($abs_n)) {
        Math::GMPz::Rmpz_set_str($A, $A_str, 10);

        # A must carry the exact sign of n since B is strictly positive
        if ($sgn_n < 0) {
            Math::GMPz::Rmpz_neg($A, $A);
        }

        Math::GMPz::Rmpz_divexact($B, $n, $A);
        Math::GMPz::Rmpz_mul($A2, $A, $A);
        Math::GMPz::Rmpz_sub($D, $A2, $B);

        # Use _ui functions to avoid allocating an mpz object for 3
        if (Math::GMPz::Rmpz_divisible_ui_p($D, 3)) {
            Math::GMPz::Rmpz_divexact_ui($C, $D, 3);

            # Delta = A^2 - 4*C (computed directly in $Delta)
            Math::GMPz::Rmpz_mul_ui($Delta, $C, 4);
            Math::GMPz::Rmpz_sub($Delta, $A2, $Delta);

            if (Math::GMPz::Rmpz_sgn($Delta) >= 0 && Math::GMPz::Rmpz_perfect_square_p($Delta)) {
                Math::GMPz::Rmpz_sqrt($sqrt_D, $Delta);

                Math::GMPz::Rmpz_add($num1, $A, $sqrt_D);
                Math::GMPz::Rmpz_sub($num2, $A, $sqrt_D);

                if (Math::GMPz::Rmpz_even_p($num1) && Math::GMPz::Rmpz_even_p($num2)) {
                    Math::GMPz::Rmpz_tdiv_q_2exp($num1, $num1, 1);
                    Math::GMPz::Rmpz_tdiv_q_2exp($num2, $num2, 1);

                    # x >= y ensures we don't output permutations like (3, 4) and (4, 3)
                    if (Math::GMPz::Rmpz_cmp($num1, $num2) >= 0) {

                        # Instantiate fresh objects only when recording a valid pair
                        my $x = Math::GMPz::Rmpz_init_set($num2);
                        my $y = Math::GMPz::Rmpz_init_set($num1);

                        push @solutions, _array([bless(\$x), bless(\$y)]);
                    }
                }
            }
        }
    }

    @solutions = sort { ${$a->[0]} <=> ${$b->[0]} } @solutions;

    return _array(\@solutions);
}

# Sum of two k-gonal numbers
sub sum_of_polygonals {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // return _array();
    $k = _any2mpz($$k) // return _array();

    Math::GMPz::Rmpz_sgn($n) >= 0 or return _array();

    # Defined only for k >= 3
    if (Math::GMPz::Rmpz_cmp_ui($k, 3) < 0) {
        return _array();
    }

    if (Math::GMPz::Rmpz_cmp_ui($k, 4) == 0) {
        return ((bless \$n)->sum_of_squares);
    }

    my $k_minus_2 = Math::GMPz::Rmpz_init();
    my $k_minus_4 = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sub_ui($k_minus_2, $k, 2);
    Math::GMPz::Rmpz_sub_ui($k_minus_4, $k, 4);

    my $x = Math::GMPz::Rmpz_init();
    my $y = Math::GMPz::Rmpz_init();

    # Calculate N = 8 * (k - 2) * n + 2 * (k - 4)^2
    my $N = Math::GMPz::Rmpz_init_set($n);
    Math::GMPz::Rmpz_mul_2exp($N, $N, 3);
    Math::GMPz::Rmpz_mul($N, $N,         $k_minus_2);
    Math::GMPz::Rmpz_mul($x, $k_minus_4, $k_minus_4);
    Math::GMPz::Rmpz_addmul_ui($N, $x, 2);

    # Retrieve all base square representations
    my @sq_sols = @{(bless \$N)->sum_of_squares};

    my @results;
    my %seen;

    my $den = Math::GMPz::Rmpz_init_set($k_minus_2);
    Math::GMPz::Rmpz_mul_2exp($den, $den, 1);

    for my $sol (@sq_sols) {

        my $X = _any2mpz(${$sol->[0]});
        my $Y = _any2mpz(${$sol->[1]});

        my @cand_X = ($X);
        push @cand_X, -$X if Math::GMPz::Rmpz_sgn($X) != 0;

        my @cand_Y = ($Y);
        push @cand_Y, -$Y if Math::GMPz::Rmpz_sgn($Y) != 0;

        my @pairs;
        for my $cx (@cand_X) {
            for my $cy (@cand_Y) {
                push @pairs, [$cx, $cy];
                push @pairs, [$cy, $cx] if Math::GMPz::Rmpz_cmp($X, $Y) != 0;
            }
        }

        # Validate configurations that map back to valid integers
        for my $pair (@pairs) {
            my ($A, $B) = @$pair;

            Math::GMPz::Rmpz_add($x, $A, $k_minus_4);
            Math::GMPz::Rmpz_add($y, $B, $k_minus_4);

            if (    Math::GMPz::Rmpz_divisible_p($x, $den)
                and Math::GMPz::Rmpz_divisible_p($y, $den)) {
                Math::GMPz::Rmpz_divexact($x, $x, $den);
                Math::GMPz::Rmpz_divexact($y, $y, $den);

                if (Math::GMPz::Rmpz_sgn($x) >= 0 and Math::GMPz::Rmpz_sgn($y) >= 0) {

                    if (Math::GMPz::Rmpz_cmp($y, $x) < 0) {
                        ($x, $y) = ($y, $x);
                    }

                    my $key = join(';', Math::GMPz::Rmpz_get_str($x, 16), Math::GMPz::Rmpz_get_str($y, 16));

                    if (!$seen{$key}++) {
                        push @results, [Math::GMPz::Rmpz_init_set($x), Math::GMPz::Rmpz_init_set($y)];
                    }
                }
            }
        }
    }

    @results = sort { $a->[0] <=> $b->[0] } @results;

    _array(
        [
         map {
             _array([map { _set_int($_) } @$_])
           } @results
        ]
    );
}

sub primitive_pythagorean_triples {
    my ($limit) = @_;

    $limit = _any2ui($$limit) // return _array();

    my @triples;

    # Seed the tree with the fundamental [3, 4, 5] triple
    my @queue = ([3, 4, 5]);

    while (@queue) {
        my $t = shift @queue;
        my ($A, $B, $C) = @$t;

        # Prune branches where the perimeter exceeds the limit
        my $p = $A + $B + $C;
        next if $p > $limit;

        # Standardize order (a < b)
        push @triples, _array([map { bless \$_ } ($A < $B ? ($A, $B, $C) : ($B, $A, $C))]);

        push @queue, [$A - 2 * $B + 2 * $C, 2 * $A - $B + 2 * $C, 2 * $A - 2 * $B + 3 * $C];
        push @queue, [$A + 2 * $B + 2 * $C, 2 * $A + $B + 2 * $C, 2 * $A + 2 * $B + 3 * $C];
        push @queue, [-$A + 2 * $B + 2 * $C, -2 * $A + $B + 2 * $C, -2 * $A + 2 * $B + 3 * $C];
    }

    # Sort results structurally
    @triples = sort { ${$a->[0]} <=> ${$b->[0]} || ${$a->[1]} <=> ${$b->[1]} } @triples;
    return _array(\@triples);
}

sub _modular_rational {
    my ($n, $m) = @_;

    if (ref($n) ne 'Math::GMPq') {
        $n = _any2mpq($n) // return;
    }

    state $z = Math::GMPz::Rmpz_init_nobless();

    my $t = Math::GMPz::Rmpz_init();
    Math::GMPq::Rmpq_get_den($z, $n);
    Math::GMPz::Rmpz_invert($t, $z, $m) or return;
    Math::GMPq::Rmpq_get_num($z, $n);
    Math::GMPz::Rmpz_mul($t, $t, $z);

    return $t;
}

sub powmod {
    my ($n, $k, $m) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = $$n;
    $k = $$k;
    $m = $$m;

    if (FAST_MODE and !ref($m) and !ref($n) and !ref($k) and $m > 0) {
        my $r = (
                 HAS_PRIME_UTIL
                 ? Math::Prime::Util::powmod($n, $k, $m)
                 : Math::Prime::Util::GMP::powmod($n, $k, $m)
                ) // goto &nan;
        return bless \$r;
    }

    $k = _any2mpz($k) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    if (ref($n) ne 'Math::GMPz') {
        if (__is_int__($n)) {
            $n = _any2mpz($n) // goto &nan;
        }
        else {
            $n = _modular_rational($n, $m) // goto &nan;
        }
    }

    state $r = Math::GMPz::Rmpz_init_nobless();

    if (Math::GMPz::Rmpz_sgn($k) < 0) {
        Math::GMPz::Rmpz_invert($r, $n, $m) or goto &nan;
    }

    Math::GMPz::Rmpz_fits_ulong_p($k)
      ? Math::GMPz::Rmpz_powm_ui($r, $n, Math::GMPz::Rmpz_get_ui($k), $m)
      : Math::GMPz::Rmpz_powm($r, $n, $k, $m);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);

    bless \$r2;
}

*expmod = \&powmod;

sub complex_cmp {
    my ($x_re, $x_im, $y_re, $y_im) = @_;

    $x_im //= ZERO;
    $y_re //= ZERO;
    $y_im //= ZERO;

    ref($x_im) eq __PACKAGE__ or _valid(\$x_im);
    ref($y_re) eq __PACKAGE__ or _valid(\$y_re);
    ref($y_im) eq __PACKAGE__ or _valid(\$y_im);

#<<<
    my $cmp = (
           (__cmp__($$x_re, $$y_re) // return undef)
        || (__cmp__($$x_im, $$y_im) // return undef)
    );
#>>>

    ($cmp ? ($cmp == 1 ? ONE : MONE) : ZERO);
}

sub complex_mod {
    my ($x, $y, $m) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($m) eq __PACKAGE__ or _valid(\$m);
    ((bless \__mod__($$x, $$m)), (bless \__mod__($$y, $$m)));
}

*cmod = \&complex_mod;

sub complex_add {
    my ($x_re, $x_im, $y_re, $y_im) = @_;

    $x_im //= ZERO;
    $y_re //= ZERO;
    $y_im //= ZERO;

    ref($x_im) eq __PACKAGE__ or _valid(\$x_im);
    ref($y_re) eq __PACKAGE__ or _valid(\$y_re);
    ref($y_im) eq __PACKAGE__ or _valid(\$y_im);

    ((bless \__add__($$x_re, $$y_re)), (bless \__add__($$x_im, $$y_im)));
}

*cadd = \&complex_add;

sub complex_sub {
    my ($x_re, $x_im, $y_re, $y_im) = @_;

    $x_im //= ZERO;
    $y_re //= ZERO;
    $y_im //= ZERO;

    ref($x_im) eq __PACKAGE__ or _valid(\$x_im);
    ref($y_re) eq __PACKAGE__ or _valid(\$y_re);
    ref($y_im) eq __PACKAGE__ or _valid(\$y_im);

    ((bless \__sub__($$x_re, $$y_re)), (bless \__sub__($$x_im, $$y_im)));
}

*csub = \&complex_sub;

sub complex_mul {
    my ($x_re, $x_im, $y_re, $y_im) = @_;

    # (a + b*i) * (x + y*i) = (a*x - b*y) + (a*y + b*x)*i

    $x_im //= ZERO;
    $y_re //= ZERO;
    $y_im //= ZERO;

    ref($x_im) eq __PACKAGE__ or _valid(\$x_im);
    ref($y_re) eq __PACKAGE__ or _valid(\$y_re);
    ref($y_im) eq __PACKAGE__ or _valid(\$y_im);

#<<<
    (
        (bless \__sub__(__mul__($$x_re, $$y_re), __mul__($$x_im, $$y_im))),
        (bless \__add__(__mul__($$x_re, $$y_im), __mul__($$x_im, $$y_re))),
    );
#>>>
}

*cmul = \&complex_mul;

sub complex_div {
    my ($x_re, $x_im, $y_re, $y_im) = @_;

    if (!defined($y_re) and !defined($y_im)) {    # ceil division
        return $x_re->idiv_ceil($x_im);
    }

    # (a + b*i) / (x + y*i) = (a*x + b*y)/(x^2 + y^2) + (b*x - a*y)/(x^2 + y^2)*i

    $x_im //= ZERO;
    $y_re //= ZERO;
    $y_im //= ZERO;

    ref($x_im) eq __PACKAGE__ or _valid(\$x_im);
    ref($y_re) eq __PACKAGE__ or _valid(\$y_re);
    ref($y_im) eq __PACKAGE__ or _valid(\$y_im);

    my $den = __add__(__mul__($$y_re, $$y_re), __mul__($$y_im, $$y_im));

#<<<
    (
        (bless \__div__(__add__(__mul__($$x_re, $$y_re), __mul__($$x_im, $$y_im)), $den)),
        (bless \__div__(__sub__(__mul__($$x_im, $$y_re), __mul__($$x_re, $$y_im)), $den)),
    );
#>>>
}

*cdiv = \&complex_div;

sub complex_inv {
    my ($re, $im) = @_;

    $im //= ZERO;

    ref($im) eq __PACKAGE__ or _valid(\$im);

    my $den = __add__(__mul__($$re, $$re), __mul__($$im, $$im));

#<<<
    (
        (bless \__div__(        $$re,  $den)),
        (bless \__div__(__neg__($$im), $den)),
    );
#>>>
}

*cinv = \&complex_inv;

sub complex_invmod {
    my ($x, $y, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = _any2mpz($$x) // return (nan(), nan());
    $y = _any2mpz($$y) // return (nan(), nan());
    $m = _any2mpz($$m) // return (nan(), nan());

    my $t = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_mul($t, $x, $x);
    Math::GMPz::Rmpz_addmul($t, $y, $y);

    if (Math::GMPz::Rmpz_invert($t, $t, $m)) {

        my $c0 = Math::GMPz::Rmpz_init();
        my $c1 = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_mul($c0, $x, $t);
        Math::GMPz::Rmpz_mul($c1, $y, $t);
        Math::GMPz::Rmpz_neg($c1, $c1);
        Math::GMPz::Rmpz_mod($c0, $c0, $m);
        Math::GMPz::Rmpz_mod($c1, $c1, $m);

        return ((bless \$c0), (bless \$c1));
    }

    return (nan(), nan());    # no inverse
}

*cinvmod = \&complex_invmod;

sub complex_ipow {
    my ($x, $y, $n) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    $x = _any2mpz($$x) // return (nan(), nan());
    $y = _any2mpz($$y) // return (nan(), nan());
    $n = _any2mpz($$n) // return (nan(), nan());

    my $c0 = Math::GMPz::Rmpz_init_set_ui(1);
    my $c1 = Math::GMPz::Rmpz_init_set_ui(0);

    $x = Math::GMPz::Rmpz_init_set($x);
    $y = Math::GMPz::Rmpz_init_set($y);

    my $neg = 0;
    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);
        $neg = 1;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    foreach my $k (0 .. Math::GMPz::Rmpz_sizeinbase($n, 2) - 1) {

        if (Math::GMPz::Rmpz_tstbit($n, $k)) {
            Math::GMPz::Rmpz_set($t, $c0);

            Math::GMPz::Rmpz_mul($c0, $c0, $x);
            Math::GMPz::Rmpz_submul($c0, $c1, $y);

            Math::GMPz::Rmpz_mul($c1, $c1, $x);
            Math::GMPz::Rmpz_addmul($c1, $t, $y);
        }

        Math::GMPz::Rmpz_mul($t, $x, $y);
        Math::GMPz::Rmpz_mul_2exp($t, $t, 1);

        Math::GMPz::Rmpz_mul($x, $x, $x);
        Math::GMPz::Rmpz_submul($x, $y, $y);
        Math::GMPz::Rmpz_set($y, $t);
    }

    my ($r1, $r2) = ((bless \$c0), (bless \$c1));
    ($r1, $r2) = complex_inv($r1, $r2) if $neg;
    ($r1, $r2);
}

sub complex_pow {
    my ($x, $y, $n) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    if (__is_int__($$x) and __is_int__($$y)) {
        return complex_ipow($x, $y, $n);
    }

    $x = $$x;
    $y = $$y;
    $n = _any2mpz($$n) // return (nan(), nan());

    my $neg = 0;
    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);
        $neg = 1;
    }

    my $c0 = 1;
    my $c1 = 0;

#<<<
    foreach my $k (0 .. Math::GMPz::Rmpz_sizeinbase($n, 2) - 1) {

        if (Math::GMPz::Rmpz_tstbit($n, $k)) {
            ($c0, $c1) = (
                __sub__(__mul__($c0, $x), __mul__($c1, $y)),
                __add__(__mul__($c0, $y), __mul__($c1, $x)),
            );
        }

        ($x, $y) = (
            __sub__(__mul__($x, $x), __mul__($y, $y)),
            __mul__(__mul__($x, $y), 2),
        );
    }
#>>>

    my ($r1, $r2) = ((bless \$c0), (bless \$c1));
    ($r1, $r2) = complex_inv($r1, $r2) if $neg;
    ($r1, $r2);
}

*cpow = \&complex_pow;

sub complex_powmod {
    my ($x, $y, $n, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = $$x;
    $y = $$y;

    $n = _any2mpz($$n) // return (nan(), nan());
    $m = _any2mpz($$m) // return (nan(), nan());

    Math::GMPz::Rmpz_sgn($m) || return (nan(), nan());

    # Identities for fractional x = a/b, y = c/d:
    #   ((a/b) + (c/d)*i)^n = ((a*d + b*c*i) / (b*d))^n
    #                       = (a*d + b*c*i)^n * (b*d)^(-n)

    # We use:
    #   ((a/b) + (c/d)*i)^n mod m = (a*invmod(b,m) + c*invmod(d, m)*i)^n mod m

    if (ref($x) ne 'Math::GMPz') {
        if (__is_int__($x)) {
            $x = _any2mpz($x) // return (nan(), nan());
        }
        else {
            $x = _modular_rational($x, $m) // return (nan(), nan());
        }
    }

    if (ref($y) ne 'Math::GMPz') {
        if (__is_int__($y)) {
            $y = _any2mpz($y) // return (nan(), nan());
        }
        else {
            $y = _modular_rational($y, $m) // return (nan(), nan());
        }
    }

    $x = _any2mpz($x) // return (nan(), nan());
    $y = _any2mpz($y) // return (nan(), nan());

    my $c0 = Math::GMPz::Rmpz_init_set_ui(1);
    my $c1 = Math::GMPz::Rmpz_init_set_ui(0);

    $x = Math::GMPz::Rmpz_init_set($x);
    $y = Math::GMPz::Rmpz_init_set($y);

    state $t = Math::GMPz::Rmpz_init_nobless();

    # Handle negative exponent
    if (Math::GMPz::Rmpz_sgn($n) < 0) {

        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);

        my $t = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_mul($t, $x, $x);
        Math::GMPz::Rmpz_addmul($t, $y, $y);

        if (Math::GMPz::Rmpz_invert($t, $t, $m)) {

            Math::GMPz::Rmpz_mul($c0, $x, $t);
            Math::GMPz::Rmpz_mul($c1, $y, $t);
            Math::GMPz::Rmpz_neg($c1, $c1);
            Math::GMPz::Rmpz_mod($c0, $c0, $m);
            Math::GMPz::Rmpz_mod($c1, $c1, $m);

            Math::GMPz::Rmpz_set($x, $c0);
            Math::GMPz::Rmpz_set($y, $c1);

            Math::GMPz::Rmpz_set_ui($c0, 1);
            Math::GMPz::Rmpz_set_ui($c1, 0);
        }
        else {    # no inverse exists
            return (nan(), nan());
        }
    }

    foreach my $k (0 .. Math::GMPz::Rmpz_sizeinbase($n, 2) - 1) {

        if (Math::GMPz::Rmpz_tstbit($n, $k)) {
            Math::GMPz::Rmpz_set($t, $c0);

            Math::GMPz::Rmpz_mul($c0, $c0, $x);
            Math::GMPz::Rmpz_submul($c0, $c1, $y);

            Math::GMPz::Rmpz_mul($c1, $c1, $x);
            Math::GMPz::Rmpz_addmul($c1, $t, $y);

            Math::GMPz::Rmpz_mod($c0, $c0, $m);
            Math::GMPz::Rmpz_mod($c1, $c1, $m);
        }

        Math::GMPz::Rmpz_mul($t, $x, $y);
        Math::GMPz::Rmpz_mul_2exp($t, $t, 1);

        Math::GMPz::Rmpz_powm_ui($x, $x, 2, $m);
        Math::GMPz::Rmpz_powm_ui($y, $y, 2, $m);

        Math::GMPz::Rmpz_sub($x, $x, $y);
        Math::GMPz::Rmpz_mod($y, $t, $m);
    }

    ((bless \$c0), (bless \$c1));
}

*cpowmod = \&complex_powmod;

sub invmod {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($y) and !ref($x) and $x != 0) {
        my $r = (
                 HAS_PRIME_UTIL
                 ? Math::Prime::Util::invmod($x, $y)
                 : Math::Prime::Util::GMP::invmod($x, $y)
                ) // goto &nan;
        return bless \$r;
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_invert($r, $x, $y) || (goto &nan);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub divmod {
    my ($x, $y, $m) = @_;

    if (defined($m)) {    # modular division

        ref($y) eq __PACKAGE__ or _valid(\$y);
        ref($m) eq __PACKAGE__ or _valid(\$m);

        $x = $$x;
        $y = $$y;
        $m = $$m;

        if (FAST_MODE and !ref($m) and $m > 0 and !ref($y) and $y > 1 and !ref($x)) {
            my $r =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divmod($x, $y, $m)
              : Math::Prime::Util::GMP::divmod($x, $y, $m);
            if (defined($r)) {
                return bless \$r;
            }
        }

        $x = _any2mpz($x) // goto &nan;
        $y = _any2mpz($y) // goto &nan;
        $m = _any2mpz($m) // goto &nan;

        my $r = Math::GMPz::Rmpz_init();

        if (Math::GMPz::Rmpz_invert($r, $y, $m)) {
            Math::GMPz::Rmpz_mul($r, $r, $x);
            Math::GMPz::Rmpz_mod($r, $r, $m);
        }
        else {
            goto &nan;
        }

        return bless \$r;
    }

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($y) and $y > 0 and !ref($x)) {
        my ($q, $r) = (
                       HAS_PRIME_UTIL
                       ? Math::Prime::Util::divrem($x, $y)
                       : Math::Prime::Util::GMP::divrem($x, $y)
                      );
        return (_set_int($q), _set_int($r));
    }

    $x = _any2mpz($x) // return (nan(), nan());
    $y = _any2mpz($y) // return (nan(), nan());

    Math::GMPz::Rmpz_sgn($y)
      || return (nan(), nan());

    my $q = Math::GMPz::Rmpz_init();
    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_divmod($q, $r, $x, $y);
    ((bless \$q), (bless \$r));
}

*divrem = \&divmod;

sub and {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and (!ref($x) && $x >= 0) and (!ref($y) && $y >= 0)) {
        my $r = $x & $y;
        return bless \$r;
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_and($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub or {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and (!ref($x) && $x >= 0) and (!ref($y) && $y >= 0)) {
        my $r = $x | $y;
        return bless \$r;
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_ior($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub xor {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and (!ref($x) && $x >= 0) and (!ref($y) && $y >= 0)) {
        my $r = $x ^ $y;
        return bless \$r;
    }

    $x = _any2mpz($x) // (goto &nan);
    $y = _any2mpz($y) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_xor($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub not {
    my ($x) = @_;

    $x = _any2mpz($$x) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_com($r, $x);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub bit {
    my ($x, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $x = _any2mpz($$x) // return undef;
    $k = _any2ui($$k)  // return undef;

    Math::GMPz::Rmpz_tstbit($x, $k) ? ONE : ZERO;
}

*getbit  = \&bit;
*testbit = \&bit;

sub setbit {
    my ($x, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $x = _any2mpz($$x) // return undef;
    $k = _any2ui($$k)  // return undef;

    my $r = Math::GMPz::Rmpz_init_set($x);
    Math::GMPz::Rmpz_setbit($r, $k);
    bless \$r;
}

sub flipbit {
    my ($x, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $x = _any2mpz($$x) // return undef;
    $k = _any2ui($$k)  // return undef;

    my $r = Math::GMPz::Rmpz_init_set($x);

    Math::GMPz::Rmpz_tstbit($r, $k)
      ? Math::GMPz::Rmpz_clrbit($r, $k)
      : Math::GMPz::Rmpz_setbit($r, $k);

    bless \$r;
}

sub clearbit {
    my ($x, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $x = _any2mpz($$x) // return undef;
    $k = _any2ui($$k)  // return undef;

    my $r = Math::GMPz::Rmpz_init_set($x);
    Math::GMPz::Rmpz_clrbit($r, $k);
    bless \$r;
}

sub bit_scan0 {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return undef } : 0;
    $n = _any2mpz($$n) // return undef;

    _set_int(Math::GMPz::Rmpz_scan0($n, $k));
}

sub bit_scan1 {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return undef } : 0;
    $n = _any2mpz($$n) // return undef;

    _set_int(Math::GMPz::Rmpz_scan1($n, $k));
}

sub ramanujan_tau {
    my ($n) = @_;
    $n = _big2uistr($$n) // goto &nan;

    if ($n > 1e5 and $USE_PARI_GP) {

        my @terms;
        foreach my $pp (_factor_exp($n)) {
            my ($p, $e) = @$pp;
            my $pe = ($e == 1)  ? $p                                    : Math::Prime::Util::GMP::powint($p, $e);
            my $r  = ($p > 1e5) ? _execute_pari_gp("ramanujantau($pe)") : Math::Prime::Util::GMP::ramanujan_tau($pe);
            push @terms, $r;
        }

        return _set_int(Math::Prime::Util::GMP::vecprod(@terms));
    }

    _set_int(Math::Prime::Util::GMP::ramanujan_tau($n));
}

*RamanujanTau = \&ramanujan_tau;

sub ramanujan_sum {
    my ($n, $k) = @_;

    #
    ## c_k(n) = μ(k/gcd(n, k)) * φ(k) / φ(k/gcd(n, k))
    #

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    # Make `k` positive if it is negative
    if (Math::GMPz::Rmpz_sgn($k) < 0) {
        $k = Math::GMPz::Rmpz_init_set($k);
        Math::GMPz::Rmpz_neg($k, $k);
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_gcd($r, $n, $k);
    Math::GMPz::Rmpz_divexact($r, $k, $r) if Math::GMPz::Rmpz_sgn($r);

    my $r_str = Math::GMPz::Rmpz_get_str($r, 10);
    my $mu    = Math::Prime::Util::GMP::moebius($r_str) || return ZERO;

    if (Math::GMPz::Rmpz_cmp($r, $k) == 0) {
        return ($mu == 1 ? ONE : MONE);
    }

    my $k_str = Math::GMPz::Rmpz_get_str($k, 10);
    my $phi_k = Math::Prime::Util::GMP::totient($k_str);
    my $phi_r = Math::Prime::Util::GMP::totient($r_str);

    (FAST_MODE and $phi_k < ULONG_MAX)
      ? Math::GMPz::Rmpz_set_ui($r, $phi_k)
      : Math::GMPz::Rmpz_set_str($r, $phi_k, 10);

    if (FAST_MODE and $phi_r < ULONG_MAX) {
        Math::GMPz::Rmpz_divexact_ui($r, $r, $phi_r);
    }
    else {
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_set_str($t, $phi_r, 10);
        Math::GMPz::Rmpz_divexact($r, $r, $t);
    }

    Math::GMPz::Rmpz_neg($r, $r) if ($mu == -1);
    bless \$r;
}

*RamanujanSum = \&ramanujan_sum;

sub subfactorial {    # OEIS: A000166
    my ($x, $y) = @_;

    my $m = _any2ui($$x) // goto &nan;
    my $k = defined($y) ? do { ref($y) eq __PACKAGE__ or _valid(\$y); _any2si($$y) // goto &nan } : 0;

    my $n = $m - $k;

    return ZERO if ($k < 0);
    return ONE  if ($n == 0);
    return ZERO if ($n < 0);

    my $z = Math::GMPz::Rmpz_init();

    if ($n >= 30000) {

        state $logtau = CORE::log(6.28318530717958647692528676655900576839433879875);

        my $logn = CORE::log($n);
        my $prec = 4 + CORE::int(($n * $logn + ($logn + $logtau) / 2 - $n) / CORE::log(2));

        Math::GMPz::Rmpz_fac_ui($z, $n);

        my $f = Math::MPFR::Rmpfr_init2($prec);
        Math::MPFR::Rmpfr_set_ui($f, 1, $round_z);
        Math::MPFR::Rmpfr_exp($f, $f, $round_z);
        Math::MPFR::Rmpfr_z_div($f, $z, $f, $round_z);
        Math::MPFR::Rmpfr_add_d($f, $f, 0.5, $round_z);
        Math::MPFR::Rmpfr_floor($f, $f);
        Math::MPFR::Rmpfr_get_z($z, $f, $round_z);
    }
    else {
        Math::GMPz::Rmpz_set_str($z, Math::Prime::Util::GMP::subfactorial($n), 10);
    }

    if ($k != 0) {
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_bin_uiui($t, $m, $k);
        Math::GMPz::Rmpz_mul($z, $z, $t);
    }

    bless \$z;
}

*rencontres = \&subfactorial;

sub factorial_sum {
    my ($n) = @_;
    $n = _any2ui($$n) // goto &nan;
    _set_int(Math::Prime::Util::GMP::factorial_sum($n));
}

*left_factorial = \&factorial_sum;

sub superprimorial {    # A006939
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    $n || return ONE;

    my @terms;
    my $k = 1;

    foreach my $p (@{_primes(2, ${$_[0]->nth_prime})}) {
        my $z = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_ui_pow_ui($z, $p, $n - $k + 1);
        push @terms, $z;
        ++$k;
    }

    bless \_binsplit(\@terms, \&__mul__);
}

sub lnsuperprimorial {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPFR::Rmpfr_set_ui($r, 0, $ROUND);

    my $k = 1;
    foreach my $p (@{_primes(2, ${$_[0]->nth_prime})}) {
        Math::MPFR::Rmpfr_set_ui($t, $p, $ROUND);
        Math::MPFR::Rmpfr_log($t, $t, $ROUND);
        Math::MPFR::Rmpfr_mul_ui($t, $t, $n - $k + 1, $ROUND);
        Math::MPFR::Rmpfr_add($r, $r, $t, $ROUND);
        ++$k;
    }

    bless \$r;
}

*superprimorial_ln  = \&lnsuperprimorial;
*superprimorial_log = \&lnsuperprimorial;

sub superfactorial {    # A000178
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my @terms;
    foreach my $k (2 .. $n) {
        my $z = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_ui_pow_ui($z, $k, $n - $k + 1);
        push @terms, $z;
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub lnsuperfactorial {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPFR::Rmpfr_set_ui($r, 0, $ROUND);

    foreach my $k (2 .. $n) {
        Math::MPFR::Rmpfr_set_ui($t, $k, $ROUND);
        Math::MPFR::Rmpfr_log($t, $t, $ROUND);
        Math::MPFR::Rmpfr_mul_ui($t, $t, $n - $k + 1, $ROUND);
        Math::MPFR::Rmpfr_add($r, $r, $t, $ROUND);
    }

    bless \$r;
}

*superfactorial_ln  = \&lnsuperfactorial;
*superfactorial_log = \&lnsuperfactorial;

sub hyperfactorial {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my @terms;
    foreach my $k (2 .. $n) {
        my $z = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_ui_pow_ui($z, $k, $k);
        push @terms, $z;
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub lnhyperfactorial {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    my $t = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

    Math::MPFR::Rmpfr_set_ui($r, 0, $ROUND);

    foreach my $k (2 .. $n) {
        Math::MPFR::Rmpfr_set_ui($t, $k, $ROUND);
        Math::MPFR::Rmpfr_log($t, $t, $ROUND);
        Math::MPFR::Rmpfr_mul_ui($t, $t, $k, $ROUND);
        Math::MPFR::Rmpfr_add($r, $r, $t, $ROUND);
    }

    bless \$r;
}

*hyperfactorial_ln  = \&lnhyperfactorial;
*hyperfactorial_log = \&lnhyperfactorial;

sub factorial {
    my ($n) = @_;
    $n = _any2ui($$n) // goto &nan;

    state @FAC_CACHE;
    if ($n <= 2000) {
        return $FAC_CACHE[$n] //= do {
            my $r = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_fac_ui($r, $n);
            $r = Math::GMPz::Rmpz_get_ui($r) if ($n <= ((INTSIZE <= 32) ? 12 : 20));
            bless \$r, __PACKAGE__;
        };
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_fac_ui($r, $n);
    $r = Math::GMPz::Rmpz_get_ui($r) if ($n <= ((INTSIZE <= 32) ? 12 : 20));
    bless \$r;
}

*fac = \&factorial;

sub kempner {
    my ($n) = @_;

    my $v = _big2pistr($$n) // return ONE;

    # The Smarandache/Kempner function is defined for positive integers.
    # For n <= 1, S(n) = 1.
    if ($v eq '1') {
        return ONE;
    }

    # Identity: S(p) = p for prime numbers
    if (_is_prob_prime($v)) {
        return _set_int($v);
    }

    my @factors = _factor_exp($v);

    # Track the maximum S(p^k) across all prime factors
    my $max_m = Math::GMPz::Rmpz_init_set_ui(0);

    state $p = Math::GMPz::Rmpz_init_nobless();
    state $m = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    foreach my $pp (@factors) {
        my $k = $pp->[1];
        Math::GMPz::Rmpz_set_str($p, $pp->[0], 10);

        # Build the Kempner expansion coefficients array: a_j = (p^j - 1)/(p - 1)
        # and store the corresponding powers of p to avoid redundant exponentiations.
        my @a     = (1);
        my @p_pow = ($p);

        if (Math::GMPz::Rmpz_cmp_ui($p, $k) < 0) {

            my $p_ui = Math::GMPz::Rmpz_get_ui($p);

            while (1) {

                my $next_a = $a[-1] * $p_ui + 1;

                if ($next_a > $k) {
                    last;
                }

                push @a, $next_a;
                my $next_p = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($next_p, $p_pow[-1], $p);
                push @p_pow, $next_p;
            }
        }

        # Greedy decomposition of the exponent k
        my $rem_k = $k;
        Math::GMPz::Rmpz_set_ui($m, 0);

        for (my $i = $#a ; $i >= 0 ; $i--) {
            my $ai = $a[$i];
            my $c  = CORE::int($rem_k / $ai);

            if ($c > 0) {
                $rem_k -= $c * $ai;
                Math::GMPz::Rmpz_mul_ui($t, $p_pow[$i], $c);
                Math::GMPz::Rmpz_add($m, $m, $t);
            }
        }

        # Maximize: S(n) = max( S(p_i^e_i) )
        if (Math::GMPz::Rmpz_cmp($m, $max_m) > 0) {
            Math::GMPz::Rmpz_set($max_m, $m);
        }
    }

    bless \$max_m;
}

*smarandache = \&kempner;

sub factorialmod {
    my ($n, $m) = @_;
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = $$n;
    $m = $$m;

    if (HAS_PRIME_UTIL and _fits_ulong($m) and _fits_ulong($n)) {
        my $r = Math::Prime::Util::factorialmod(_get_ulong($n), _get_ulong($m)) // goto &nan;
        return bless \$r;
    }

    $n = _any2mpz($n) // goto &nan;
    $m = _any2mpz($m) // goto &nan;

    my $r;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($m) and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $r = Math::Prime::Util::factorialmod(Math::GMPz::Rmpz_get_ui($n), Math::GMPz::Rmpz_get_ui($m)) // goto &nan;
    }
    else {
        $r = Math::Prime::Util::GMP::factorialmod(_big2uistr($n) // (goto &nan), _big2uistr($m) // (goto &nan)) // goto &nan;
    }

    _set_int($r);
}

sub double_factorial {
    my ($x) = @_;
    my $ui  = _any2ui($$x) // (goto &nan);
    my $z   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_2fac_ui($z, $ui);
    bless \$z;
}

*dfac       = \&double_factorial;
*dfactorial = \&double_factorial;

sub multi_factorial {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    my $ui1 = _any2ui($$x) // (goto &nan);
    my $ui2 = _any2ui($$y) // (goto &nan);
    my $z   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mfac_uiui($z, $ui1, $ui2);
    bless \$z;
}

*mfac       = \&multi_factorial;
*mfactorial = \&multi_factorial;

#
## falling_factorial(x, +y) = binomial(x, y) * y!
## falling_factorial(x, -y) = 1/falling_factorial(x + y, y)
#

sub falling_factorial {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // (goto &nan);
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    my $r = Math::GMPz::Rmpz_init_set($x);

    if ($y < 0) {
        Math::GMPz::Rmpz_add_ui($r, $r, CORE::abs($y));
    }

    Math::GMPz::Rmpz_bin_ui($r, $r, CORE::abs($y));

    Math::GMPz::Rmpz_sgn($r) || do {
        ($y < 0) ? (goto &nan) : (return ZERO);
    };

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_fac_ui($t, CORE::abs($y));
    Math::GMPz::Rmpz_mul($r, $r, $t);

    if ($y < 0) {
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_z($q, $r);
        Math::GMPq::Rmpq_inv($q, $q);
        return bless \$q;
    }

    bless \$r;
}

#
## rising_factorial(x, +y) = binomial(x + y - 1, y) * y!
## rising_factorial(x, -y) = 1/rising_factorial(x - y, y)
#

sub rising_factorial {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // (goto &nan);
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    my $r = Math::GMPz::Rmpz_init_set($x);
    Math::GMPz::Rmpz_add_ui($r, $r, CORE::abs($y));
    Math::GMPz::Rmpz_sub_ui($r, $r, 1);

    if ($y < 0) {
        Math::GMPz::Rmpz_sub_ui($r, $r, CORE::abs($y));
    }

    Math::GMPz::Rmpz_bin_ui($r, $r, CORE::abs($y));

    Math::GMPz::Rmpz_sgn($r) || do {
        ($y < 0) ? (goto &nan) : (return ZERO);
    };

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_fac_ui($t, CORE::abs($y));
    Math::GMPz::Rmpz_mul($r, $r, $t);

    if ($y < 0) {
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_z($q, $r);
        Math::GMPq::Rmpq_inv($q, $q);
        return bless \$q;
    }

    bless \$r;
}

sub primorial {
    my ($x) = @_;
    my $ui  = _any2ui($$x) // goto &nan;
    my $z   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_primorial_ui($z, $ui);
    bless \$z;
}

sub primorial_inflation {    # A108951(n)
    my ($n) = @_;

    $n = $$n;

    if (ref($n) eq 'Math::GMPq') {    # handle rational inputs (not very efficient)

        my $num = Math::GMPz::Rmpz_init();
        my $den = Math::GMPz::Rmpz_init();

        Math::GMPq::Rmpq_get_num($num, $n);
        Math::GMPq::Rmpq_get_den($den, $n);

        my $t1 = (bless \$num)->primorial_inflation;
        my $t2 = (bless \$den)->primorial_inflation;

        return $t1->div($t2);
    }

    $n = _big2uistr($n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;
        ($p < ULONG_MAX) || goto &nan;

        my $primorial = ($p <= 1e5) ? _cached_primorial($p, 1e4) : do {
            Math::GMPz::Rmpz_primorial_ui($t, $p);
            $t;
        };

        if ($e > 1) {
            Math::GMPz::Rmpz_pow_ui($t, $primorial, $e);
            push @terms, Math::GMPz::Rmpz_init_set($t);
        }
        else {
            push @terms, Math::GMPz::Rmpz_init_set($primorial);
        }
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub primorial_deflation {    # A319626(n) / A319627(n)
    my ($n) = @_;

    $n = _big2uistr($$n) // goto &nan;

    my @terms;

    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        my $q = ($p <= 2) ? 1 : _prev_prime($p);
        my $t = Math::GMPq::Rmpq_init();

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPq::Rmpq_set_ui($t, $p, $q);
        }
        else {
            Math::GMPq::Rmpq_set_str($t, "$p/$q", 10);
        }

        if ($e > 1) {
            Math::GMPq::Rmpq_pow_ui($t, $t, $e);
        }

        push @terms, $t;
    }

    @terms || return ONE;
    my $prod = _binsplit(\@terms, \&__mul__);

    if (ref($prod) eq 'Math::GMPq' and Math::GMPq::Rmpq_integer_p($prod)) {
        $prod = _mpq2mpz($prod);
    }

    bless \$prod;
}

sub pn_primorial {
    my ($x) = @_;
    _set_int(Math::Prime::Util::GMP::pn_primorial(_any2ui($$x) // goto &nan));
}

sub lucas {
    my ($x) = @_;
    my $ui  = _any2ui($$x) // (goto &nan);
    my $z   = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_lucnum_ui($z, $ui);
    bless \$z;
}

*Lucas = \&lucas;

sub lucasu {
    my ($p, $q, $n) = @_;

    ref($q) eq __PACKAGE__ or _valid(\$q);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    $p = _big2istr($$p)  // goto &nan;
    $q = _big2istr($$q)  // goto &nan;
    $n = _big2uistr($$n) // goto &nan;

    _set_int(Math::Prime::Util::GMP::lucasu($p, $q, $n));
}

*lucasU = \&lucasu;
*LucasU = \&lucasu;

sub lucasv {
    my ($p, $q, $n) = @_;

    ref($q) eq __PACKAGE__ or _valid(\$q);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    $p = _big2istr($$p)  // goto &nan;
    $q = _big2istr($$q)  // goto &nan;
    $n = _big2uistr($$n) // goto &nan;

    _set_int(Math::Prime::Util::GMP::lucasv($p, $q, $n));
}

*lucasV = \&lucasv;
*LucasV = \&lucasv;

sub pell {
    my ($n) = @_;
    Sidef::Types::Number::Number::lucasu(TWO, MONE, $n);
}

sub pell_lucas {
    my ($n) = @_;
    Sidef::Types::Number::Number::lucasv(TWO, MONE, $n);
}

sub jacobsthal {
    my ($n) = @_;
    state $TWO_NEG = (TWO)->neg;
    Sidef::Types::Number::Number::lucasu(ONE, $TWO_NEG, $n);
}

sub jacobsthal_lucas {
    my ($n) = @_;
    state $TWO_NEG = (TWO)->neg;
    Sidef::Types::Number::Number::lucasv(ONE, $TWO_NEG, $n);
}

sub padovan {
    my ($n) = @_;
    Sidef::Math::Math->linear_rec(_array([ZERO, ONE, ONE]), _array([ONE, ZERO, ZERO]), $n);
}

sub padovanmod {
    my ($n, $m) = @_;
    ref($m) eq __PACKAGE__ or _valid(\$m);
    Sidef::Math::Math->linear_recmod(_array([ZERO, ONE, ONE]), _array([ONE, ZERO, ZERO]), $n, $m);
}

sub perrin {
    my ($n) = @_;
    Sidef::Math::Math->linear_rec(_array([ZERO, ONE, ONE]), _array([THREE, ZERO, TWO]), $n);
}

sub perrinmod {
    my ($n, $m) = @_;
    ref($m) eq __PACKAGE__ or _valid(\$m);
    Sidef::Math::Math->linear_recmod(_array([ZERO, ONE, ONE]), _array([THREE, ZERO, TWO]), $n, $m);
}

sub __lucasUVmod__ {
    my ($P, $Q, $n, $m) = @_;

    my $U1 = Math::GMPz::Rmpz_init_set_ui(1);

    my ($V1, $V2) = (Math::GMPz::Rmpz_init_set_ui(2), Math::GMPz::Rmpz_init_set($P));
    my ($Q1, $Q2) = (Math::GMPz::Rmpz_init_set_ui(1), Math::GMPz::Rmpz_init_set_ui(1));

    Math::GMPz::Rmpz_sgn($n) == 0
      and return (Math::GMPz::Rmpz_init_set_ui(0), Math::GMPz::Rmpz_init_set_ui(2));

    my $t = Math::GMPz::Rmpz_init();
    my $s = Math::GMPz::Rmpz_scan1($n, 0);

    Math::GMPz::Rmpz_div_2exp($t, $n, $s + 1);

    my $nbits = Math::GMPz::Rmpz_sizeinbase($t, 2);
    for (my $i = $nbits - 1 ; $i >= 0 ; $i--) {
        my $bit = Math::GMPz::Rmpz_tstbit($t, $i);

        Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
        Math::GMPz::Rmpz_mod($Q1, $Q1, $m);

        if ($bit) {
            Math::GMPz::Rmpz_mul($Q2, $Q1, $Q);
            Math::GMPz::Rmpz_mul($U1, $U1, $V2);
            Math::GMPz::Rmpz_mul($V1, $V1, $V2);

            Math::GMPz::Rmpz_powm_ui($V2, $V2, 2, $m);
            Math::GMPz::Rmpz_submul($V1, $Q1, $P);
            Math::GMPz::Rmpz_submul_ui($V2, $Q2, 2);

            Math::GMPz::Rmpz_mod($V1, $V1, $m);
            Math::GMPz::Rmpz_mod($U1, $U1, $m);
        }
        else {
            Math::GMPz::Rmpz_set($Q2, $Q1);
            Math::GMPz::Rmpz_mul($U1, $U1, $V1);
            Math::GMPz::Rmpz_mul($V2, $V2, $V1);
            Math::GMPz::Rmpz_sub($U1, $U1, $Q1);

            Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $m);
            Math::GMPz::Rmpz_submul($V2, $Q1, $P);
            Math::GMPz::Rmpz_submul_ui($V1, $Q2, 2);

            Math::GMPz::Rmpz_mod($V2, $V2, $m);
            Math::GMPz::Rmpz_mod($U1, $U1, $m);
        }
    }

    Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
    Math::GMPz::Rmpz_mul($Q2, $Q1, $Q);
    Math::GMPz::Rmpz_mul($U1, $U1, $V1);
    Math::GMPz::Rmpz_mul($V1, $V1, $V2);
    Math::GMPz::Rmpz_sub($U1, $U1, $Q1);
    Math::GMPz::Rmpz_submul($V1, $Q1, $P);
    Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);

    for (1 .. $s) {
        Math::GMPz::Rmpz_mul($U1, $U1, $V1);
        Math::GMPz::Rmpz_mod($U1, $U1, $m);
        Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $m);
        Math::GMPz::Rmpz_submul_ui($V1, $Q1, 2);
        Math::GMPz::Rmpz_powm_ui($Q1, $Q1, 2, $m);
    }

    Math::GMPz::Rmpz_mod($U1, $U1, $m);
    Math::GMPz::Rmpz_mod($V1, $V1, $m);

    return ($U1, $V1);
}

sub __lucasVmod__ {
    my ($P, $Q, $n, $m) = @_;

    my ($V1, $V2) = (Math::GMPz::Rmpz_init_set_ui(2), Math::GMPz::Rmpz_init_set($P));
    my ($Q1, $Q2) = (Math::GMPz::Rmpz_init_set_ui(1), Math::GMPz::Rmpz_init_set_ui(1));

    my $nbits = Math::GMPz::Rmpz_sizeinbase($n, 2);
    for (my $i = $nbits - 1 ; $i >= 0 ; $i--) {
        my $bit = Math::GMPz::Rmpz_tstbit($n, $i);

        Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
        Math::GMPz::Rmpz_mod($Q1, $Q1, $m);

        if ($bit) {
            Math::GMPz::Rmpz_mul($Q2, $Q1, $Q);
            Math::GMPz::Rmpz_mul($V1, $V1, $V2);
            Math::GMPz::Rmpz_powm_ui($V2, $V2, 2, $m);
            Math::GMPz::Rmpz_submul($V1, $P, $Q1);
            Math::GMPz::Rmpz_submul_ui($V2, $Q2, 2);
            Math::GMPz::Rmpz_mod($V1, $V1, $m);
        }
        else {
            Math::GMPz::Rmpz_set($Q2, $Q1);
            Math::GMPz::Rmpz_mul($V2, $V2, $V1);
            Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $m);
            Math::GMPz::Rmpz_submul($V2, $P, $Q1);
            Math::GMPz::Rmpz_submul_ui($V1, $Q2, 2);
            Math::GMPz::Rmpz_mod($V2, $V2, $m);
        }
    }

    Math::GMPz::Rmpz_mod($V1, $V1, $m);

    return ($V1, $V2);
}

sub _modular_lucas_UV {
    my ($P, $Q, $n, $m) = @_;

    if (    (ref($P) ? (Math::GMPz::Rmpz_cmpabs_ui($P, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($P) < $LUCAS_PQ_LIMIT))
        and (ref($Q) ? (Math::GMPz::Rmpz_cmpabs_ui($Q, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($Q) < $LUCAS_PQ_LIMIT))) {
        my ($U, $V);
        if (    FAST_MODE
            and HAS_PRIME_UTIL
            and (!ref($m) or Math::GMPz::Rmpz_fits_ulong_p($m))
            and (!ref($n) or Math::GMPz::Rmpz_fits_ulong_p($n))) {
            eval {
                ($U, $V) =
                  Math::Prime::Util::lucas_sequence(
                                                    (ref($m) ? Math::GMPz::Rmpz_get_ui($m) : $m),
                                                    (ref($P) ? Math::GMPz::Rmpz_get_si($P) : $P),
                                                    (ref($Q) ? Math::GMPz::Rmpz_get_si($Q) : $Q),
                                                    (ref($n) ? Math::GMPz::Rmpz_get_ui($n) : $n)
                                                   );
            };
        }
        else {
            eval { ($U, $V) = Math::Prime::Util::GMP::lucas_sequence($m, $P, $Q, $n) };
        }
        defined($U) && defined($V) && return (map { _str2obj($_) } ($U, $V));
    }

    $P = _any2mpz($P) if !ref($P);
    $Q = _any2mpz($Q) if !ref($Q);
    $n = _any2mpz($n) if !ref($n);
    $m = _any2mpz($m) if !ref($m);

    state $D = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($D, $P, $P);
    Math::GMPz::Rmpz_submul_ui($D, $Q, 4);

    # When `gcd(P*P - 4*Q, m) = 1`, we can use a faster algorithm
    if (Math::GMPz::Rmpz_invert($D, $D, $m)) {

        my ($V1, $V2) = __lucasVmod__($P, $Q, $n, $m);

        Math::GMPz::Rmpz_mul_2exp($V2, $V2, 1);
        Math::GMPz::Rmpz_submul($V2, $V1, $P);
        Math::GMPz::Rmpz_mul($V2, $V2, $D);
        Math::GMPz::Rmpz_mod($V2, $V2, $m);

        return ($V2, $V1);
    }

    __lucasUVmod__($P, $Q, $n, $m);
}

sub _modular_lucas_U {
    my ($P, $Q, $n, $m) = @_;

    if (    (ref($P) ? (Math::GMPz::Rmpz_cmpabs_ui($P, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($P) < $LUCAS_PQ_LIMIT))
        and (ref($Q) ? (Math::GMPz::Rmpz_cmpabs_ui($Q, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($Q) < $LUCAS_PQ_LIMIT))) {
        my ($U, $V);
        if (    FAST_MODE
            and HAS_PRIME_UTIL
            and (!ref($m) or Math::GMPz::Rmpz_fits_ulong_p($m))
            and (!ref($n) or Math::GMPz::Rmpz_fits_ulong_p($n))) {
            if (HAS_PRIME_UTIL) {
                eval {
                    $U =
                      Math::Prime::Util::lucasumod(
                                                   (ref($P) ? Math::GMPz::Rmpz_get_si($P) : $P),
                                                   (ref($Q) ? Math::GMPz::Rmpz_get_si($Q) : $Q),
                                                   (ref($n) ? Math::GMPz::Rmpz_get_ui($n) : $n),
                                                   (ref($m) ? Math::GMPz::Rmpz_get_ui($m) : $m),
                                                  );
                };
            }
            else {
                eval {
                    ($U, $V) =
                      Math::Prime::Util::lucas_sequence(
                                                        (ref($m) ? Math::GMPz::Rmpz_get_ui($m) : $m),
                                                        (ref($P) ? Math::GMPz::Rmpz_get_si($P) : $P),
                                                        (ref($Q) ? Math::GMPz::Rmpz_get_si($Q) : $Q),
                                                        (ref($n) ? Math::GMPz::Rmpz_get_ui($n) : $n),
                                                       );
                };
            }
        }
        else {
            eval { $U = Math::Prime::Util::GMP::lucasumod($P, $Q, $n, $m) };
        }
        defined($U) && return _str2obj($U);
    }

    $P = _any2mpz($P) if !ref($P);
    $Q = _any2mpz($Q) if !ref($Q);
    $n = _any2mpz($n) if !ref($n);
    $m = _any2mpz($m) if !ref($m);

    state $D = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($D, $P, $P);
    Math::GMPz::Rmpz_submul_ui($D, $Q, 4);

    # When `gcd(P*P - 4*Q, m) = 1`, we can use a faster algorithm
    if (Math::GMPz::Rmpz_invert($D, $D, $m)) {

        my ($V1, $V2) = __lucasVmod__($P, $Q, $n, $m);

        Math::GMPz::Rmpz_mul_2exp($V2, $V2, 1);
        Math::GMPz::Rmpz_submul($V2, $V1, $P);
        Math::GMPz::Rmpz_mul($V2, $V2, $D);
        Math::GMPz::Rmpz_mod($V2, $V2, $m);

        return $V2;
    }

    (__lucasUVmod__($P, $Q, $n, $m))[0];
}

sub _modular_lucas_V {
    my ($P, $Q, $n, $m) = @_;

    if (    (ref($P) ? (Math::GMPz::Rmpz_cmpabs_ui($P, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($P) < $LUCAS_PQ_LIMIT))
        and (ref($Q) ? (Math::GMPz::Rmpz_cmpabs_ui($Q, $LUCAS_PQ_LIMIT) < 0) : (CORE::abs($Q) < $LUCAS_PQ_LIMIT))) {
        my ($U, $V);
        if (    FAST_MODE
            and HAS_PRIME_UTIL
            and (!ref($m) or Math::GMPz::Rmpz_fits_ulong_p($m))
            and (!ref($n) or Math::GMPz::Rmpz_fits_ulong_p($n))) {
            if (HAS_PRIME_UTIL) {
                eval {
                    $V =
                      Math::Prime::Util::lucasvmod(
                                                   (ref($P) ? Math::GMPz::Rmpz_get_si($P) : $P),
                                                   (ref($Q) ? Math::GMPz::Rmpz_get_si($Q) : $Q),
                                                   (ref($n) ? Math::GMPz::Rmpz_get_ui($n) : $n),
                                                   (ref($m) ? Math::GMPz::Rmpz_get_ui($m) : $m),
                                                  );
                };
            }
            else {
                eval {
                    ($U, $V) =
                      Math::Prime::Util::lucas_sequence(
                                                        (ref($m) ? Math::GMPz::Rmpz_get_ui($m) : $m),
                                                        (ref($P) ? Math::GMPz::Rmpz_get_si($P) : $P),
                                                        (ref($Q) ? Math::GMPz::Rmpz_get_si($Q) : $Q),
                                                        (ref($n) ? Math::GMPz::Rmpz_get_ui($n) : $n),
                                                       );
                };
            }
        }
        else {
            eval { $V = Math::Prime::Util::GMP::lucasvmod($P, $Q, $n, $m) };
        }
        defined($V) && return _str2obj($V);
    }

    $P = _any2mpz($P) if !ref($P);
    $Q = _any2mpz($Q) if !ref($Q);
    $n = _any2mpz($n) if !ref($n);
    $m = _any2mpz($m) if !ref($m);

    (__lucasVmod__($P, $Q, $n, $m))[0];
}

sub lucasumod {
    my ($P, $Q, $n, $m) = @_;

    ref($Q) eq __PACKAGE__ or _valid(\$Q);
    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $P = _any2mpz($$P) // goto &nan;
    $Q = _any2mpz($$Q) // goto &nan;
    $n = _any2mpz($$n) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    # undefined for m=0
    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    # U_0(P, Q) = 0
    Math::GMPz::Rmpz_sgn($n) || return ZERO;

    # undefined for n < 0
    Math::GMPz::Rmpz_sgn($n) < 0 && goto &nan;

    bless \_modular_lucas_U($P, $Q, $n, $m);
}

*LucasUmod = \&lucasumod;
*lucasUmod = \&lucasumod;

sub lucasvmod {
    my ($P, $Q, $n, $m) = @_;

    ref($Q) eq __PACKAGE__ or _valid(\$Q);
    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $P = _any2mpz($$P) // goto &nan;
    $Q = _any2mpz($$Q) // goto &nan;
    $n = _any2mpz($$n) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    # undefined for m=0
    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    # undefined for n < 0
    Math::GMPz::Rmpz_sgn($n) < 0 && goto &nan;

    bless \_modular_lucas_V($P, $Q, $n, $m);
}

*LucasVmod = \&lucasvmod;
*lucasVmod = \&lucasvmod;

sub lucasuvmod {
    my ($P, $Q, $n, $m) = @_;

    ref($Q) eq __PACKAGE__ or _valid(\$Q);
    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $P = _any2mpz($$P) // goto &nan;
    $Q = _any2mpz($$Q) // goto &nan;
    $n = _any2mpz($$n) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    # undefined for m=0
    Math::GMPz::Rmpz_sgn($m) || return (nan(), nan());

    # undefined for n < 0
    Math::GMPz::Rmpz_sgn($n) < 0 && return (nan(), nan());

    my ($U, $V) = _modular_lucas_UV($P, $Q, $n, $m);

    ((bless \$U), (bless \$V));
}

*LucasUVmod = \&lucasuvmod;
*lucasUVmod = \&lucasuvmod;

#
## Chebyshev polynomials: T_n(x)
#

sub chebyshevt {
    my ($n, $x) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = $$n;
    $n = _any2si($n) // (goto &nan) if ref($n);
    $n = -$n                        if $n < 0;
    $n == 0 and return ONE;

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $polynomial = 1;
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
    }

    $n == 1 and return $x;

    if (ref($x) eq __PACKAGE__) {
        if (ref($$x) eq 'Math::GMPz' or (__is_rat__($$x) and __is_int__($$x))) {
            return _set_int(Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::lucasv(__mul__(2, $$x), 1, $n), 2));
        }
    }

    if ($polynomial and $n >= 10 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("polchebyshev($n,1)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    # T_n(x) = 1/2 * ((x - sqrt(x^2 - 1))^n + (x + sqrt(x^2 - 1))^n)

    my $e = _set_int($n);
    my $Q = Sidef::Types::Number::Quadratic->new(ZERO, ONE, $x->mul($x)->dec);
    my $r = $Q->neg->add($x)->pow($e);

    $r->a;
}

*chebyshevT = \&chebyshevt;
*ChebyshevT = \&chebyshevt;

#
## Chebyshev polynomials: U_n(x)
#

sub chebyshevu {
    my ($n, $x) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = $$n;
    $n = _any2si($n) // (goto &nan) if ref($n);
    $n == 0 and return ONE;

    my $negative = 0;

    if ($n < 0) {

        $n == -1 and return ZERO;
        $n == -2 and return MONE;

        $n        = -$n - 2;
        $negative = 1;
    }

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $polynomial = 1;
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
    }

    if (ref($x) eq __PACKAGE__) {
        if (ref($$x) eq 'Math::GMPz' or (__is_rat__($$x) and __is_int__($$x))) {
            my $r = _set_int(Math::Prime::Util::GMP::lucasu(__mul__($$x, 2), 1, $n + 1));
            $r = $r->neg if $negative;
            return $r;
        }
    }

    if ($polynomial and $n >= 10 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("polchebyshev($n,2)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    # U_n(x) = ((x + sqrt(x^2 - 1))^(n+1) - (x - sqrt(x^2 - 1))^(n+1)) / (2 * sqrt(x^2 - 1))

    my $e = _set_int($n + 1);
    my $Q = Sidef::Types::Number::Quadratic->new(ZERO, ONE, $x->mul($x)->dec);

    my $r = $Q->add($x)->pow($e)->b;
    $r = $r->neg if $negative;
    $r;
}

*ChebyshevU = \&chebyshevu;
*chebyshevU = \&chebyshevu;

#
## Modular Chebyshev polynomials: T_n(x) mod m
#

sub chebyshevTmod {
    my ($n, $x, $m) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($x) eq __PACKAGE__ or _valid(\$x);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = _any2mpz($$n) // goto &nan;
    $x = $$x;
    $m = _any2mpz($$m) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);    # copy
        Math::GMPz::Rmpz_abs($n, $n);
    }

    if (Math::GMPz::Rmpz_odd_p($m) and (ref($x) eq 'Math::GMPz' or (__is_rat__($x) and __is_int__($x)))) {
        return _set_int(2 * $x)->lucasVmod(ONE, (bless \$n), (bless \$m))->divmod(TWO, (bless \$m));
    }

    # T_n(x) = 1/2 * ((x - sqrt(x^2 - 1))^n + (x + sqrt(x^2 - 1))^n)

    my $Q = Sidef::Types::Number::Quadratic->new(ZERO, ONE, bless \__dec__(__mul__($x, $x)));
    my $r = ((bless \$x)->sub($Q))->powmod((bless \$n), (bless \$m));

    $r->a->mod(bless \$m);
}

#
## Modular Chebyshev polynomials: U_n(x) mod m
#

sub chebyshevUmod {
    my ($n, $x, $m) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);
    ref($x) eq __PACKAGE__ or _valid(\$x);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = _any2mpz($$n) // goto &nan;
    $x = $$x;
    $m = _any2mpz($$m) // goto &nan;

    my $negative = 0;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {

        if (Math::GMPz::Rmpz_cmp_si($n, -1) == 0) {
            return ((ZERO)->mod(bless \$m));
        }

        if (Math::GMPz::Rmpz_cmp_si($n, -2) == 0) {
            return ((MONE)->mod(bless \$m));
        }

        $n        = -$n - 2;
        $negative = 1;
    }

    if (ref($x) eq 'Math::GMPz' or (__is_rat__($x) and __is_int__($x))) {
        my $r = _set_int(2 * $x)->lucasUmod(ONE, (bless \$n)->inc, (bless \$m));
        $r = $r->neg->mod(bless \$m) if $negative;
        return $r;
    }

    # U_n(x) = ((x + sqrt(x^2 - 1))^(n+1) - (x - sqrt(x^2 - 1))^(n+1)) / (2 * sqrt(x^2 - 1))

    my $Q = Sidef::Types::Number::Quadratic->new(ZERO, ONE, bless \__dec__(__mul__($x, $x)));
    my $r = ((bless \$x)->add($Q))->powmod((bless \$n)->inc, (bless \$m))->b;

    $r = $r->neg if $negative;
    $r->mod(bless \$m);
}

#
## Legendre polynomials: P_n(x)
#

sub legendre_polynomial {
    my ($n, $x) = @_;

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
        $polynomial = 1;
    }

    $n = _any2ui($$n) // goto &nan;

    $n == 0 && return ONE;
    $n == 1 && return $x;

    if ($polynomial and $n >= 10 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("pollegendre($n)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    my ($x1, $x2) = ($x->dec, $x->inc);

    if (!$polynomial) {
        $x1 = $$x1;
        $x2 = $$x2;
    }

    my $t = Math::GMPz::Rmpz_init();
    my $w = Math::GMPz::Rmpz_init_set_ui(1);

    my @terms;
    foreach my $k (0 .. $n) {

        Math::GMPz::Rmpz_mul($t, $w, $w);

        if ($polynomial) {
            push @terms, $x1->pow(_set_int($n - $k))->mul($x2->pow(_set_int($k)))->mul(bless \$t);
        }
        else {
            push @terms, __mul__(__mul__(__pow__($x1, $n - $k), __pow__($x2, $k)), $t);
        }

        Math::GMPz::Rmpz_mul_ui($w, $w, $n - $k);
        Math::GMPz::Rmpz_divexact_ui($w, $w, $k + 1);
    }

    if ($polynomial) {
        my $sum = _binsplit(\@terms, \&Sidef::Types::Number::Polynomial::add);
        Math::GMPz::Rmpz_set_ui($t, 0);
        Math::GMPz::Rmpz_setbit($t, $n);
        return $sum->div(bless \$t);
    }

    my $sum = _binsplit(\@terms, \&__add__);
    Math::GMPz::Rmpz_set_ui($t, 0);
    Math::GMPz::Rmpz_setbit($t, $n);
    bless \__div__($sum, $t);
}

*LegendreP = \&legendre_polynomial;
*legendrep = \&legendre_polynomial;
*legendreP = \&legendre_polynomial;

#
## The physicists' Hermite polynomials H_n(x)
#

sub hermiteH {
    my ($n, $x) = @_;

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
        $polynomial = 1;
    }

    $n = _any2ui($$n) // goto &nan;

    if ($polynomial and $n >= 15 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("polhermite($n)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    $n == 0 && return ONE;
    $x = $x->add($x);
    $n == 1 && return $x;

    my $t = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init_set_ui(1);

    my $v = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_fac_ui($v, $n);

    my @terms;
    foreach my $m (0 .. $n >> 1) {

        Math::GMPz::Rmpz_mul($t, $v, $u);
        Math::GMPz::Rmpz_neg($t, $t) if ($m & 1);

        if ($polynomial) {
            push @terms, $x->pow(_set_int($n - ($m << 1)))->div(bless \$t);
        }
        else {
            push @terms, __div__(__pow__($$x, $n - ($m << 1)), $t);
        }

        my $d = ($n - ($m << 1)) * ($n - ($m << 1) - 1);
        Math::GMPz::Rmpz_divexact_ui($v, $v, $d) if $d;
        Math::GMPz::Rmpz_mul_ui($u, $u, $m + 1);
    }

    Math::GMPz::Rmpz_fac_ui($v, $n);

    if ($polynomial) {
        my $sum = _binsplit(\@terms, \&Sidef::Types::Number::Polynomial::add);
        return $sum->mul(bless \$v);
    }

    my $sum = _binsplit(\@terms, \&__add__);
    bless \__mul__($sum, $v);
}

*HermiteH            = \&hermiteH;
*hermite_polynomialH = \&hermiteH;

#
## The probabilists' Hermite polynomials He_n(x)
#

sub hermiteHe {
    my ($n, $x) = @_;

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
        $polynomial = 1;
    }

    $n = _any2ui($$n) // goto &nan;

    if ($polynomial and $n >= 15 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("polhermite($n)")) {
            my $poly   = Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res));
            my @coeffs = @{$poly->coeffs};
            @coeffs = map { [$_->[0], $_->[1]->shift_right(_set_int(($n + ${$_->[0]}) >> 1))] } @coeffs;
            return Sidef::Types::Number::Polynomial->new(\@coeffs)->eval($x);
        }
    }

    $n == 0 && return ONE;
    $n == 1 && return $x;

    my $t = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init_set_ui(1);

    my $v = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_fac_ui($v, $n);

    my @terms;
    foreach my $m (0 .. $n >> 1) {

        Math::GMPz::Rmpz_mul($t, $v, $u);
        Math::GMPz::Rmpz_mul_2exp($t, $t, $m);
        Math::GMPz::Rmpz_neg($t, $t) if ($m & 1);

        if ($polynomial) {
            push @terms, $x->pow(_set_int($n - ($m << 1)))->div(bless \$t);
        }
        else {
            push @terms, __div__(__pow__($$x, $n - ($m << 1)), $t);
        }

        my $d = ($n - ($m << 1)) * ($n - ($m << 1) - 1);
        Math::GMPz::Rmpz_divexact_ui($v, $v, $d) if $d;
        Math::GMPz::Rmpz_mul_ui($u, $u, $m + 1);
    }

    Math::GMPz::Rmpz_fac_ui($v, $n);

    if ($polynomial) {
        my $sum = _binsplit(\@terms, \&Sidef::Types::Number::Polynomial::add);
        return $sum->mul(bless \$v);
    }

    my $sum = _binsplit(\@terms, \&__add__);
    bless \__mul__($sum, $v);
}

*HermiteHe            = \&hermiteHe;
*hermite_polynomialHe = \&hermiteHe;

#
## Laguerre polynomials: L_n(x)
#

sub laguerreL {
    my ($n, $x) = @_;

    my $polynomial = 0;

    if (defined($x) and ref($x) ne 'Sidef::Types::Number::Polynomial') {
        ref($x) eq __PACKAGE__ or _valid(\$x);
    }
    else {
        $x //= Sidef::Types::Number::Polynomial->new(1 => ONE);
        $polynomial = 1;
    }

    $n = _any2ui($$n) // goto &nan;
    $n || return ONE;

    if ($polynomial and $n >= 50 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("pollaguerre($n)")) {
            return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res))->eval($x);
        }
    }

    my $t = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init_set_ui(1);
    my $w = Math::GMPz::Rmpz_init_set_ui(1);

    my @terms;
    foreach my $k (0 .. $n) {

        if ($k & 1) {
            Math::GMPz::Rmpz_neg($t, $w);
        }
        else {
            Math::GMPz::Rmpz_set($t, $w);
        }

        if ($polynomial) {
            push @terms, $x->pow(_set_int($k))->mul(bless \$t)->div(bless \$u);
        }
        else {
            push @terms, __div__(__mul__(__pow__($$x, $k), $t), $u);
        }

        Math::GMPz::Rmpz_mul_ui($u, $u, $k + 1);
        Math::GMPz::Rmpz_mul_ui($w, $w, $n - $k);
        Math::GMPz::Rmpz_divexact_ui($w, $w, $k + 1);
    }

    $polynomial
      ? _binsplit(\@terms, \&Sidef::Types::Number::Polynomial::add)
      : (bless \_binsplit(\@terms, \&__add__));
}

*laguerre            = \&laguerreL;
*Laguerre            = \&laguerreL;
*LaguerreL           = \&laguerreL;
*laguerre_polynomial = \&laguerreL;

sub fibonaccimod {
    my ($n, $k, $m) = @_;

    if (defined($m)) {
        _valid(\$k, \$m);

        $k = _any2ui($$k) // (goto &nan);

        if ($k == 2) {
            return $n->fibmod($m);
        }

        $n = _any2mpz($$n) // goto &nan;
        $m = _any2mpz($$m) // goto &nan;

        Math::GMPz::Rmpz_sgn($n) >= 0 or goto &nan;
        Math::GMPz::Rmpz_sgn($m) > 0  or goto &nan;

        $k < 0 and goto &nan;

        if ($k == 0) {
            $k = 1;
        }

        # Asymptotic crossover limit based on k^2 * log_2(k)
        my $limit = ($k == 1) ? 1000 : CORE::int(4 * $k * $k * CORE::log($k) / CORE::log(2));

        if (    $limit < ULONG_MAX
            and Math::GMPz::Rmpz_fits_ulong_p($n)
            and Math::GMPz::Rmpz_cmp_ui($n, $limit) <= 0) {

            $n = Math::GMPz::Rmpz_get_ui($n);

            if ($n < $k - 1) {
                return ZERO;
            }

            # Algorithm due to M. F. Hasler, running in linear time with respect to n.
            # From: https://oeis.org/A302990

            my $K = $k + 1;
            my @f = map {
                ($_ < $k)
                  ? do {
                    my $z = Math::GMPz::Rmpz_init_set_ui(2);
                    Math::GMPz::Rmpz_powm_ui($z, $z, $_, $m);
                    $z;
                  }
                  : Math::GMPz::Rmpz_init_set_ui(1)
            } 1 .. $K;

            state $t = Math::GMPz::Rmpz_init_nobless();

            my $start = 2 * $K - 2;
            my $curr  = $start % $K;
            my $prev  = ($curr == 0) ? $K - 1 : $curr - 1;

            for my $i ($start .. $n) {
                Math::GMPz::Rmpz_mul_2exp($t, $f[$prev], 1);
                Math::GMPz::Rmpz_sub($f[$curr], $t, $f[$curr]);
                Math::GMPz::Rmpz_mod($f[$curr], $f[$curr], $m);

                $prev = $curr;
                if (++$curr == $K) {
                    $curr = 0;
                }
            }

            my $r = $f[$n % $K];
            return bless \$r;
        }

        # Sublinear algorithm with time-complexity based on k.
        return Sidef::Math::Math->linear_recmod(_array([(ONE) x $k]), _array([(ZERO) x ($k - 1), ONE]), (bless \$n), (bless \$m));
    }
    else {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $m = $k;
    }

    $n = _any2mpz($$n) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;
    Math::GMPz::Rmpz_sgn($m) > 0 or goto &nan;

    Math::GMPz::Rmpz_cmp_ui($m, 1) == 0 and return ZERO;

    bless \_modular_lucas_U(1, -1, $n, $m);
}

*fibmod        = \&fibonaccimod;
*fibonacci_mod = \&fibonaccimod;
*FibonacciMod  = \&fibonaccimod;

sub lucasmod {
    my ($n, $m) = @_;
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = _big2uistr($$n) // goto &nan;
    $m = _big2pistr($$m) // goto &nan;

    return ZERO if $m eq '1';

    my (undef, $r) = Math::Prime::Util::GMP::lucas_sequence($m, 1, -1, $n);
    _set_int($r);
}

*lucas_mod = \&lucasmod;
*LucasMod  = \&lucasmod;

sub fibonacci {
    my ($n, $k) = @_;

    $n = _any2ui($$n) // (goto &nan);

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = (_any2ui($$k) // (goto &nan)) || return ONE;

        if ($k == 2) {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_fib_ui($z, $n);
            return bless \$z;
        }

        if ($n < $k - 1) {
            return ZERO;
        }

        my $limit = CORE::int(3 * ($k**3) * CORE::log($k) / CORE::log(2));

        if ($n > $limit) {
            return Sidef::Math::Math->linear_rec(_array([(ONE) x $k]), _array([(ZERO) x ($k - 1), ONE]), (bless \$n));
        }

        # Algorithm due to M. F. Hasler, running in linear time with respect to n.
        # From: https://oeis.org/A302990
        my $K = $k + 1;
        my @f = map {
            ($_ < $k)
              ? do {
                my $z = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_setbit($z, $_);
                $z;
              }
              : Math::GMPz::Rmpz_init_set_ui(1)
        } 1 .. $K;

        state $t = Math::GMPz::Rmpz_init_nobless();

        my $start = 2 * $K - 2;
        my $curr  = $start % $K;
        my $prev  = ($curr == 0) ? $K - 1 : $curr - 1;

        for my $i ($start .. $n) {
            Math::GMPz::Rmpz_mul_2exp($t, $f[$prev], 1);
            Math::GMPz::Rmpz_sub($f[$curr], $t, $f[$curr]);

            $prev = $curr;
            if (++$curr == $K) {
                $curr = 0;
            }
        }

        my $r = $f[$n % $K];
        return bless \$r;
    }

    state @FIB_CACHE;
    if ($n <= 5000) {
        return $FIB_CACHE[$n] //= do {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_fib_ui($z, $n);
            bless \$z, __PACKAGE__;
        };
    }

    my $z = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_fib_ui($z, $n);
    bless \$z;
}

*fib       = \&fibonacci;
*Fibonacci = \&fibonacci;

sub tribonacci {
    my ($n) = @_;
    $n->fibonacci(THREE);
}

sub tetranacci {
    my ($n) = @_;
    $n->fibonacci(FOUR);
}

sub fibbinary {    # A003714: Fibbinary numbers
    my ($n) = @_;

    $n = _any2mpz($$n);
    $n = Math::GMPz::Rmpz_init_set($n);    # copy

    my $s    = Math::GMPz::Rmpz_init_set_ui(0);
    my @list = (Math::GMPz::Rmpz_init_set_ui(2), Math::GMPz::Rmpz_init_set_ui(1));

    while (1) {
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_add($t, $list[0], $list[1]);
        last if (Math::GMPz::Rmpz_cmp($t, $n) > 0);
        unshift @list, $t;
    }

    foreach my $d (@list) {
        Math::GMPz::Rmpz_mul_2exp($s, $s, 1);
        if (Math::GMPz::Rmpz_cmp($d, $n) <= 0) {
            Math::GMPz::Rmpz_add_ui($s, $s, 1);
            Math::GMPz::Rmpz_sub($n, $n, $d);
        }
    }

    bless \$s;
}

sub zeckendorf {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();
    return _array() if Math::GMPz::Rmpz_sgn($n) <= 0;

    # Global cache for Fibonacci sequence
    state $fibs = [do { state $F2 = Math::GMPz::Rmpz_init_nobless(); Math::GMPz::Rmpz_set_ui($F2, 1); $F2 },
                   do { state $F3 = Math::GMPz::Rmpz_init_nobless(); Math::GMPz::Rmpz_set_ui($F3, 2); $F3 }
                  ];

    # Expand cache dynamically if N exceeds current bounds
    while (Math::GMPz::Rmpz_cmp($fibs->[-1], $n) <= 0) {
        my $next_fib = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_add($next_fib, $fibs->[-1], $fibs->[-2]);
        push @$fibs, $next_fib;
    }

    my @representation;
    state $rem = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set($rem, $n);

    for (my $i = $#$fibs - 1 ; $i >= 0 ; $i--) {
        if (Math::GMPz::Rmpz_cmp($fibs->[$i], $rem) <= 0) {
            my $t = Math::GMPz::Rmpz_init_set($fibs->[$i]);
            push @representation, bless \$t;
            Math::GMPz::Rmpz_sub($rem, $rem, $fibs->[$i]);
            last if Math::GMPz::Rmpz_sgn($rem) == 0;
        }
    }

    return _array(\@representation);
}

sub fibonorial {
    my ($n) = @_;

    $n = _any2ui($$n) // goto &nan;

    return ONE if ($n <= 2);

    my $x = Math::GMPz::Rmpz_init_set_ui(1);
    my $y = Math::GMPz::Rmpz_init_set_ui(1);
    my $t = Math::GMPz::Rmpz_init();

    my @terms;
    foreach my $k (1 .. $n - 2) {
        Math::GMPz::Rmpz_set($t, $y);
        Math::GMPz::Rmpz_add($y, $y, $x);
        Math::GMPz::Rmpz_set($x, $t);
        push @terms, Math::GMPz::Rmpz_init_set($y);
    }

    bless \_binsplit(\@terms, \&__mul__);
}

sub _pisano_prime_power_period {
    my ($p, $e) = @_;

    # Start with the maximum possible rank of apparition modulo p: L = p - kronecker(5, p)
    my $L = Math::Prime::Util::GMP::subint($p, Math::Prime::Util::GMP::kronecker(5, $p));

    # Iteratively strip away prime factors to find the minimal period
    foreach my $pe (_factor_exp($L)) {
        my ($q, $k) = @$pe;

        for (1 .. $k) {
            my $next_L = Math::Prime::Util::GMP::divint($L, $q);

            # If dividing by q still results in a valid rank, keep the smaller L
            if (_modular_lucas_U(1, -1, $next_L, $p) == 0) {
                $L = $next_L;
            }
            else {
                last;    # q is an essential factor; stop trying to divide it out
            }
        }
    }

    $e == 1 and return $L;
    Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::powint($p, $e - 1), $L);
}

sub pisano_period {
    my ($n) = @_;

    # Assumes that Wall-Sun-Sun primes do not exist.

    $n = $$n;

    if (ref($n)) {
        $n = _any2mpz($n) // goto &nan;
        Math::GMPz::Rmpz_sgn($n) <= 0 and return ZERO;
        Math::GMPz::Rmpz_cmp_ui($n, 1) == 0 and return ONE;
    }
    else {
        $n <= 0 and return ZERO;
        $n == 1 and return ONE;
    }

    my $d = _any2mpz(Math::Prime::Util::GMP::lcm(map { _pisano_prime_power_period($_->[0], $_->[1]) } _factor_exp($n)));

    foreach my $k (0 .. 2) {

        Math::GMPz::Rmpz_add_ui($d, $d, 1);

        if (_modular_lucas_U(1, -1, $d, $n) == 1) {
            Math::GMPz::Rmpz_sub_ui($d, $d, 1);
            return bless \$d;
        }

        Math::GMPz::Rmpz_sub_ui($d, $d, 1);
        Math::GMPz::Rmpz_mul_2exp($d, $d, 1);
    }

    die "Conjecture disproved for n = $n";
}

sub motzkin {    # OEIS: A001006
    my ($n) = @_;

    $n = 1 + (_any2ui($$n) // (goto &nan));

    my $x = Math::GMPz::Rmpz_init_set_ui(0);
    my $y = Math::GMPz::Rmpz_init_set_ui(1);

    state $t = Math::GMPz::Rmpz_init_nobless();

    # Algorithm due to Peter Luschny, May 16 2016.
    foreach my $k (2 .. $n) {
        Math::GMPz::Rmpz_mul_ui($x, $x, 3 * $k * ($k - 1));
        Math::GMPz::Rmpz_mul_ui($t, $y, $k * (2 * $k - 1));
        Math::GMPz::Rmpz_add($x, $x, $t);
        Math::GMPz::Rmpz_divexact_ui($x, $x, ($k + 1) * ($k - 1));
        ($x, $y) = ($y, $x);
    }

    Math::GMPz::Rmpz_divexact_ui($y, $y, $n);
    bless \$y;
}

sub stirling {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    _set_int(Math::Prime::Util::GMP::stirling(_big2uistr($$x) // (goto &nan), _big2uistr($$y) // (goto &nan)));
}

*Stirling  = \&stirling;
*stirling1 = \&stirling;
*Stirling1 = \&stirling;

sub stirling2 {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    _set_int(Math::Prime::Util::GMP::stirling(_big2uistr($$x) // (goto &nan), _big2uistr($$y) // (goto &nan), 2));
}

*Stirling2 = \&stirling2;

sub stirling3 {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    _set_int(Math::Prime::Util::GMP::stirling(_big2uistr($$x) // (goto &nan), _big2uistr($$y) // (goto &nan), 3));
}

*Stirling3 = \&stirling3;

sub bell {
    my ($x) = @_;
    my $n = _any2ui($$x) // goto &nan;

    if ($n < 100) {
        return _set_int(Math::Prime::Util::GMP::vecsum(map { Math::Prime::Util::GMP::stirling($n, $_, 2) } 0 .. $n));
    }

    my @acc;

    my $t    = Math::GMPz::Rmpz_init();
    my $bell = Math::GMPz::Rmpz_init_set_ui(1);

    foreach my $k (1 .. $n) {

        Math::GMPz::Rmpz_set($t, $bell);

        foreach my $item (@acc) {
            Math::GMPz::Rmpz_add($t, $t, $item);
            Math::GMPz::Rmpz_set($item, $t);
        }

        unshift @acc, $bell;
        $bell = Math::GMPz::Rmpz_init_set($acc[-1]);
    }

    bless \$bell;
}

*bell_number = \&bell;
*Bell        = \&bell;

sub bellmod {
    my ($n, $m) = @_;

    ref($m) eq __PACKAGE__ or _valid(\$m);

    $n = _any2ui($$n)  // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    # For small n, Stirling numbers via Math::Prime::Util::GMP (in C) are ultra fast
    if ($n < 100) {
        return $_[0]->bell->mod($_[1]);
    }

    # Check if m fits into a native unsigned long and is safe from addition overflow
    if (Math::GMPz::Rmpz_fits_ulong_p($m)) {
        my $m_ui = Math::GMPz::Rmpz_get_ui($m);
        if ($m_ui < (~0 >> 1)) {
            my @row1    = (0) x ($n + 1);
            my @row2    = (0) x ($n + 1);
            my $bell_ui = 1;

            my ($curr, $next) = (\@row1, \@row2);
            my $acc_size = 0;

            for my $k (1 .. $n) {
                my $t = $bell_ui;
                for my $i (0 .. $acc_size - 1) {
                    $t += $curr->[$i];
                    $t -= $m_ui if $t >= $m_ui;    # Fast conditional modulo
                    $next->[$i + 1] = $t;
                }
                $next->[0] = $bell_ui;
                $bell_ui = $t if $acc_size > 0;
                $acc_size++;
                ($curr, $next) = ($next, $curr);
            }

            return bless \$bell_ui;
        }
    }

    # Fallback for massive m: For small n, computing exact bell(n) % m avoids modular overhead
    if ($n < 1000) {
        return $_[0]->bell->mod($_[1]);
    }

    my @acc;

    my $t        = Math::GMPz::Rmpz_init();
    my $bell     = Math::GMPz::Rmpz_init_set_ui(1);
    my $native_m = 0;

    if (Math::GMPz::Rmpz_fits_ulong_p($m)) {
        $m        = Math::GMPz::Rmpz_get_ui($m);
        $native_m = 1;
    }

    foreach my $k (1 .. $n) {
        Math::GMPz::Rmpz_set($t, $bell);
        foreach my $item (@acc) {
            Math::GMPz::Rmpz_add($t, $t, $item);
            $native_m
              ? Math::GMPz::Rmpz_mod_ui($t, $t, $m)
              : Math::GMPz::Rmpz_mod($t, $t, $m);
            Math::GMPz::Rmpz_set($item, $t);
        }

        unshift @acc, Math::GMPz::Rmpz_init_set($bell);
        $bell = Math::GMPz::Rmpz_init_set($acc[-1]);
    }

    bless \$bell;
}

*Bellmod = \&bellmod;

sub quadratic_formula {
    my ($A, $B, $C) = @_;

    $A //= ZERO;
    $B //= ZERO;
    $C //= ZERO;

    _valid(\$B, \$C);

    $A = $$A;
    $B = $$B;
    $C = $$C;

    #
    ## (-b ± sqrt(b^2 - 4ac)) / (2a)
    #

    my $u = __mul__($B,              $B);                # b^2
    my $t = __mul__(__mul__($A, $C), 4);                 # 4ac
    my $s = __sqrt__(_any2mpfr_mpc(__sub__($u, $t)));    # sqrt(b^2 - 4ac)

    my $n1 = __sub__($s, $B);                            #   sqrt(b^2 - 4ac) - b
    my $n2 = __neg__(__add__($s, $B));                   # -(sqrt(b^2 - 4ac) + b)

    my $d = __add__($A, $A);                             # 2a

    my $x1 = __div__($n1, $d);                           # solution 1
    my $x2 = __div__($n2, $d);                           # solution 2

    ((bless \$x1), (bless \$x2));
}

*solve_quadratic = \&quadratic_formula;

sub quadratic_formulaQ {
    my ($A, $B, $C) = @_;

    $A //= ZERO;
    $B //= ZERO;
    $C //= ZERO;

    _valid(\$B, \$C);

    #
    ## (-b ± sqrt(b^2 - 4ac)) / (2a)
    #

    my $u = $B->mul($B);                                                     # b^2
    my $t = $A->mul($C)->mul(FOUR);                                          # 4ac
    my $s = Sidef::Types::Number::Quadratic->new(ZERO, ONE, $u->sub($t));    # sqrt(b^2 - 4ac)

    my $n1 = $s->sub($B);                                                    #   sqrt(b^2 - 4ac) - b
    my $n2 = $s->add($B)->neg;                                               # -(sqrt(b^2 - 4ac) + b)

    my $d = $A->add($A);                                                     # 2a

    my $x1 = $n1->div($d);                                                   # solution 1
    my $x2 = $n2->div($d);                                                   # solution 2

    ($x1, $x2);
}

*solve_quadraticQ = \&quadratic_formulaQ;

sub cubic_formula {
    my ($A, $B, $C, $D) = @_;

    $A //= ZERO;
    $B //= ZERO;
    $C //= ZERO;
    $D //= ZERO;

    _valid(\$B, \$C, \$D);

    $A = $$A;
    $B = $$B;
    $C = $$C;
    $D = $$D;

    # Case when B = 0 and C = 0
    if (__cmp__($B, 0) == 0 and __cmp__($C, 0) == 0) {

        my $n      = __neg__($D);
        my $n_cbrt = __cbrt__(_any2mpfr_mpc($n));
        my $k_cbrt = __cbrt__(_any2mpfr_mpc($A));

        my $unit_cbrt = __cbrt__(_any2mpc(-1));
        my $r1        = __div__($n_cbrt, $k_cbrt);
        my $r2        = __neg__(__mul__($r1,        $unit_cbrt));
        my $r3        = __mul__(__mul__($unit_cbrt, $unit_cbrt), $r1);

        return ((bless \$r1), (bless \$r2), (bless \$r3));
    }

    my $A3    = __mul__($A, 3);
    my $AC    = __mul__($A, $C);
    my $BB    = __mul__($B, $B);     # b^2
    my $D0    = __sub__($BB, __mul__($AC, 3));
    my $Bp3   = __mul__($BB, $B);    # b^3
    my $ABC9  = __mul__(__mul__($AC,             $B), 9);
    my $AAD27 = __mul__(__mul__(__mul__($A, $A), $D), 27);
    my $D1    = __add__(__sub__(__add__($Bp3, $Bp3), $ABC9), $AAD27);

    my $W = __sqrt__(_any2mpfr_mpc(__sub__(__mul__($D1, $D1), __mul__(__pow__($D0, 3), 4))));
    my $M = __cbrt__(_any2mpfr_mpc(__div__(__sub__($D1, ((__sgn__($D0) || -1) == 1) ? $W : __neg__($W)), 2)));

    my @roots;

    my $R = 1;
    my $z = __div__(__sub__(__sqrt__(_any2mpc(-3)), 1), 2);

    foreach my $k (0 .. 2) {
        my $t = __mul__($M, $R);
        my $x = __neg__(__div__(__add__(__add__($B, $t), __div__($D0, $t)), $A3));
        push @roots, $x;
        $R = __mul__($R, $z) if ($k < 2);
    }

    @roots = map { bless \$_ } @roots;
    return @roots;
}

*solve_cubic = \&cubic_formula;

sub iquadratic_formula {
    my ($A, $B, $C) = @_;

    $A //= ZERO;
    $B //= ZERO;
    $C //= ZERO;

    _valid(\$B, \$C);

    $A = _any2mpz($$A) // return (&nan, &nan);
    $B = _any2mpz($$B) // return (&nan, &nan);
    $C = _any2mpz($$C) // return (&nan, &nan);

    if (Math::GMPz::Rmpz_sgn($A) == 0) {    # detect division by zero
        return (&nan, &nan);
    }

    #
    ## floor((-b ± isqrt(b^2 - 4ac)) / (2a))
    #

    my $u = Math::GMPz::Rmpz_init();
    my $t = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_mul($t, $B, $B);        # b^2
    Math::GMPz::Rmpz_mul($u, $A, $C);        # ac
    Math::GMPz::Rmpz_mul_2exp($u, $u, 2);    # 4ac
    Math::GMPz::Rmpz_sub($t, $t, $u);        # b^2 - 4ac

    if (Math::GMPz::Rmpz_sgn($t) < 0) {      # t is negative: no real solution
        return (&nan, &nan);
    }

    Math::GMPz::Rmpz_sqrt($t, $t);           # isqrt(b^2 - 4ac)

    Math::GMPz::Rmpz_sub($u, $t, $B);        #   sqrt(b^2 - 4ac) - b
    Math::GMPz::Rmpz_add($t, $t, $B);        #   sqrt(b^2 - 4ac) + b
    Math::GMPz::Rmpz_neg($t, $t);            # -(sqrt(b^2 - 4ac) + b)

    Math::GMPz::Rmpz_div($u, $u, $A);
    Math::GMPz::Rmpz_div($t, $t, $A);

    Math::GMPz::Rmpz_div_2exp($u, $u, 1);
    Math::GMPz::Rmpz_div_2exp($t, $t, 1);

    ((bless \$u), (bless \$t));
}

*isolve_quadratic          = \&iquadratic_formula;
*integer_quadratic_formula = \&iquadratic_formula;

sub modular_quadratic_formula {
    my ($x, $y, $z, $m) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    ref($z) eq __PACKAGE__ or _valid(\$z);
    ref($m) eq __PACKAGE__ or _valid(\$m);

    $x = __mod__($$x, $$m);
    $y = __mod__($$y, $$m);
    $z = __mod__($$z, $$m);

    (__is_int__($x) && __is_int__($y) && __is_int__($z))
      || return _array();

    $m = _any2mpz($$m) // return _array();
    $x = _any2mpz($x)  // return _array();
    $y = _any2mpz($y)  // return _array();
    $z = _any2mpz($z)  // return _array();

    # x must not be zero
    if (Math::GMPz::Rmpz_sgn($x) == 0) {
        return (bless \$y)->solve_lcg_all((bless \__neg__($z)), (bless \$m));
    }

    state $inv_a = Math::GMPz::Rmpz_init_nobless();

    if (Math::GMPz::Rmpz_odd_p($m) and Math::GMPz::Rmpz_invert($inv_a, $x, $m)) {

        # This part of code was translated from SymPy.

        $y = Math::GMPz::Rmpz_init_set($y);
        $z = Math::GMPz::Rmpz_init_set($z);

        Math::GMPz::Rmpz_mul($y, $y, $inv_a);
        Math::GMPz::Rmpz_mul($z, $z, $inv_a);

        if (Math::GMPz::Rmpz_odd_p($y)) {
            Math::GMPz::Rmpz_add($y, $y, $m);
        }

        Math::GMPz::Rmpz_div_2exp($y, $y, 1);

        my $D = __mod__(__sub__(__mul__($y, $y), $z), $m);
        my $S = (bless \$D)->sqrtmod_all(bless \$m);

        @$S || return $S;

        my @solutions;
        foreach my $k (@$S) {
            push @solutions, bless \__mod__(__sub__($$k, $y), $m);
        }

        return _array(\@solutions)->isort;
    }

    my $four_xm = __mul__(__mul__($x, 4), $m);    # 4*x*m

    # D = b^2 - 4*a*c (mod 4*a*m)
    my $D = __mod__(__sub__(__mul__($y, $y), __mul__(__mul__($x, $z), 4)), $four_xm);

    # Find all the solutions k to: k^2 == D (mod 4*x*m)
    my $S = (bless \$D)->sqrtmod_all(bless \$four_xm);

    @$S || return $S;

    my $two_a = __add__($x, $x);
    my $neg_b = __neg__($y);

    my @solutions;

    foreach my $k (@$S) {
        ## foreach my $u (__add__($neg_b, $$k), __sub__($neg_b, $$k)) {
        foreach my $u (__add__($neg_b, $$k)) {
            my $r = __mod__(__div__($u, $two_a), $m);
            ## __cmp__(__mod__(__add__(__add__(__mul__($x, __mul__($r, $r)), __mul__($y, $r)), $z), $m), 0) == 0 or die "error for solution: $r";
            push @solutions, bless \$r;
        }
    }

    _array(\@solutions)->isort->iuniq;
}

*solve_quadratic_mod  = \&modular_quadratic_formula;
*quadratic_congruence = \&modular_quadratic_formula;

# Generalized cubic solver modulo arbitrary composite P
sub solve_cubic_mod {
    my ($A_arg, $B_arg, $C_arg, $D_arg, $P_arg) = @_;

    ref($B_arg) eq __PACKAGE__ or _valid(\$B_arg);
    ref($C_arg) eq __PACKAGE__ or _valid(\$C_arg);
    ref($D_arg) eq __PACKAGE__ or _valid(\$D_arg);
    ref($P_arg) eq __PACKAGE__ or _valid(\$P_arg);

    $P_arg->is_pos or return _array();

    _array([map { _set_int($_) } @{_solve_polynomial_congruence_all($P_arg, [$D_arg, $C_arg, $B_arg, $A_arg])}]);
}

sub reduce_quadratic_form {
    my ($A, $B, $C) = @_;

    ref($B) eq __PACKAGE__ or _valid(\$B);
    ref($C) eq __PACKAGE__ or _valid(\$C);

    $A = _any2mpz($$A) // return _array();
    $B = _any2mpz($$B) // return _array();
    $C = _any2mpz($$C) // return _array();

    # Positive definite check (b^2 - 4ac < 0 and a > 0)
    state $disc = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($disc, $B, $B);

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($t, $A, $C);
    Math::GMPz::Rmpz_mul_ui($t, $t, 4);
    Math::GMPz::Rmpz_sub($disc, $disc, $t);

    if (Math::GMPz::Rmpz_sgn($disc) >= 0 || Math::GMPz::Rmpz_sgn($A) <= 0) {
        return _array();    # Not positive definite
    }

    # PARI/GP
    if (0 and $USE_PARI_GP) {
        my $a_str = Math::GMPz::Rmpz_get_str($A, 10);
        my $b_str = Math::GMPz::Rmpz_get_str($B, 10);
        my $c_str = Math::GMPz::Rmpz_get_str($C, 10);

        my $code = "iferr(Vec(qfbred(Qfb($a_str, $b_str, $c_str))), E, \"\")";
        if (my $res = _execute_pari_gp($code)) {
            if ($res =~ /^\[(-?\d+),\s*(-?\d+),\s*(-?\d+)\]$/) {
                my $ra = Math::GMPz::Rmpz_init_set_str($1, 10);
                my $rb = Math::GMPz::Rmpz_init_set_str($2, 10);
                my $rc = Math::GMPz::Rmpz_init_set_str($3, 10);
                return _array([bless(\$ra), bless(\$rb), bless(\$rc)]);
            }
        }
    }

    # Gauss's Reduction Algorithm
    my $ra = Math::GMPz::Rmpz_init_set($A);
    my $rb = Math::GMPz::Rmpz_init_set($B);
    my $rc = Math::GMPz::Rmpz_init_set($C);

    state $q = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    while (1) {

        # Ensure -a < b <= a
        Math::GMPz::Rmpz_mul_2exp($t, $ra, 1);    # t = 2a
        Math::GMPz::Rmpz_add($u, $rb, $ra);       # u = b + a

        # Floor division
        Math::GMPz::Rmpz_fdiv_q($q, $u, $t);
        Math::GMPz::Rmpz_submul($rb, $q, $t);     # b = b - 2a*q

        # c = (b^2 - D) / 4a
        Math::GMPz::Rmpz_mul($rc, $rb, $rb);
        Math::GMPz::Rmpz_sub($rc, $rc, $disc);
        Math::GMPz::Rmpz_div($rc, $rc, $t);
        Math::GMPz::Rmpz_div_2exp($rc, $rc, 1);

        # Apply S transformation if a > c
        if (Math::GMPz::Rmpz_cmp($ra, $rc) > 0) {
            Math::GMPz::Rmpz_swap($ra, $rc);
            Math::GMPz::Rmpz_neg($rb, $rb);
        }

        # Apply transformation if a == c and b < 0
        elsif (Math::GMPz::Rmpz_cmp($ra, $rc) == 0 && Math::GMPz::Rmpz_sgn($rb) < 0) {
            Math::GMPz::Rmpz_neg($rb, $rb);
            last;
        }
        else {
            last;
        }
    }

    # Boundary condition: if a == -b, shift to b = a
    Math::GMPz::Rmpz_neg($t, $ra);
    if (Math::GMPz::Rmpz_cmp($rb, $t) == 0) {
        Math::GMPz::Rmpz_set($rb, $ra);
    }

    return _array([bless(\$ra), bless(\$rb), bless(\$rc)]);
}

sub solve_quadratic_form {
    my ($d, $n) = @_;
    ref($n) eq __PACKAGE__ or _valid(\$n);

    # Given a positive integer `n` and a positive integer `d`,
    # returns an array with `[x,y]` solutions to the equation:
    #   x^2 + d*y^2 = n

    if ($d->is_one) {
        return $n->sum_of_squares;
    }

    # Intercept negative discriminant and route to the Generalized Pell solver
    if ($d->is_neg) {
        return $d->neg->solve_pell($n);
    }

    # FAST PATH: PARI/GP General Quadratic Form Solver
    if ($USE_PARI_GP and Math::GMPz::Rmpz_sizeinbase(_any2mpz($$n), 2) > 64) {
        my $d_str = Math::GMPz::Rmpz_get_str(_any2mpz($$d), 10);
        my $n_str = Math::GMPz::Rmpz_get_str(_any2mpz($$n), 10);

        my $code = "iferr(my(v=qfbsolve(Qfb(1,0,$d_str), $n_str, 3)); if(#v, v, \"\"), E, \"\")";
        if (my $res = _execute_pari_gp($code)) {
            return _array() if $res eq '';
            my @solutions;
            while ($res =~ /\[(-?\d+),\s*(-?\d+)\]/g) {
                my $x = Math::GMPz::Rmpz_init_set_str($1, 10);
                my $y = Math::GMPz::Rmpz_init_set_str($2, 10);
                Math::GMPz::Rmpz_abs($x, $x);
                Math::GMPz::Rmpz_abs($y, $y);
                push @solutions, _array([bless(\$x), bless(\$y)]);
            }
            if (@solutions) {
                my %seen;
                @solutions = grep { !$seen{"${$_->[0]};${$_->[1]}"}++ } @solutions;
                @solutions = sort { ${$a->[0]} <=> ${$b->[0]} } @solutions;
                return _array(\@solutions);
            }
        }
    }

    # Helper to find primitive solutions using Cornacchia-style method
    my $primitive_solutions = sub {
        my ($dd, $nn, $DD) = @_;

        if ($DD->kronecker($nn)->is_mone && _is_prob_prime($$nn)) {
            return [];
        }

        my @prim;
        foreach my $x (@{$DD->sqrtmod_all($nn)}) {
            my ($A, $B, $C) = ($nn, $x, $nn->isqrt);

            while (__cmp__($$B, $$C) > 0) {
                ($A, $B) = ($B, $A->mod($B));
            }

            my $t = $nn->sub($B->sqr);
            next unless $dd->divides($t);

            my $q = $t->idiv($dd);
            next unless $q->is_square;

            push @prim, [$B, $q->isqrt];
        }
        return \@prim;
    };

    # Handle all solutions via scaling primitive ones
    my @solutions;
    my $D = $d->neg;

    foreach my $s (@{$n->square_divisors}) {
        my $g = $s->isqrt;
        my $m = $n->idiv($s);

        my $prim_sols = $primitive_solutions->($d, $m, $D);

        foreach my $p (@$prim_sols) {
            my ($x1, $y1) = @$p;
            push @solutions, [$g->mul($x1), $g->mul($y1)];
        }
    }

    # Also include the trivial y=0 case if applicable
    if ($n->is_square) {
        push @solutions, [$n->isqrt, ZERO];
    }

    my %seen;

    @solutions = grep { !$seen{"${$_->[0]};${$_->[1]}"}++ } @solutions;
    @solutions = sort { ${$a->[0]} <=> ${$b->[0]} } @solutions;
    @solutions = map  { _array($_) } @solutions;

    return _array(\@solutions);
}

*cornacchia = \&solve_quadratic_form;

sub solve_binary_quadratic_form {
    my ($A, $B, $C, $n) = @_;

    ref($A) eq __PACKAGE__ or _valid(\$A);
    ref($B) eq __PACKAGE__ or _valid(\$B);
    ref($C) eq __PACKAGE__ or _valid(\$C);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    my $a_z = _any2mpz($$A) // return _array();
    my $b_z = _any2mpz($$B) // return _array();
    my $c_z = _any2mpz($$C) // return _array();
    my $n_z = _any2mpz($$n) // return _array();

    if (Math::GMPz::Rmpz_sgn($a_z) == 0) {
        return _array();
    }

    # Normalize a > 0 to preserve the mathematical integrity of the reduction
    if (Math::GMPz::Rmpz_sgn($a_z) < 0) {
        Math::GMPz::Rmpz_neg($a_z, $a_z);
        Math::GMPz::Rmpz_neg($b_z, $b_z);
        Math::GMPz::Rmpz_neg($c_z, $c_z);
        Math::GMPz::Rmpz_neg($n_z, $n_z);
    }

    # Reduce to X^2 + D Y^2 = 4an where X = 2ax + by, Y = y, D = 4ac - b^2
    # Now universally applicable for elliptic (D > 0) and hyperbolic (D < 0) forms.
    my $D_z = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($D_z, $a_z, $c_z);
    Math::GMPz::Rmpz_mul_ui($D_z, $D_z, 4);

    # Parabolic form fallback (D == 0)
    if (Math::GMPz::Rmpz_sgn($D_z) == 0) {
        my $scaled_n = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul($scaled_n, $a_z, $n_z);
        Math::GMPz::Rmpz_mul_ui($scaled_n, $scaled_n, 4);

        # 4an must be a perfect square
        if (!Math::GMPz::Rmpz_perfect_square_p($scaled_n)) {
            return _array();
        }

        my $S = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_sqrt($S, $scaled_n);

        my $two_a = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul_ui($two_a, $a_z, 2);

        # Extended Euclidean Algorithm: g = u*(2a) + v*(b)
        my $g = Math::GMPz::Rmpz_init();
        my $u = Math::GMPz::Rmpz_init();
        my $v = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_gcdext($g, $u, $v, $two_a, $b_z);

        my @base_sols;

        # Test both +S and -S
        for my $sign (1, -1) {
            my $curr_S = Math::GMPz::Rmpz_init_set($S);
            Math::GMPz::Rmpz_neg($curr_S, $curr_S) if $sign < 0;

            if (Math::GMPz::Rmpz_divisible_p($curr_S, $g)) {
                my $k = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_divexact($k, $curr_S, $g);

                my $x0 = Math::GMPz::Rmpz_init();
                my $y0 = Math::GMPz::Rmpz_init();

                # Base solutions: x0 = u * (S/g), y0 = v * (S/g)
                Math::GMPz::Rmpz_mul($x0, $u, $k);
                Math::GMPz::Rmpz_mul($y0, $v, $k);

                push @base_sols, _array([bless(\$x0), bless(\$y0)]);
            }
        }

        if (@base_sols) {
            my %seen;
            @base_sols = grep { !$seen{"${$_->[0]};${$_->[1]}"}++ } @base_sols;
            @base_sols = sort { ${$a->[0]} <=> ${$b->[0]} || ${$a->[1]} <=> ${$b->[1]} } @base_sols;
            return _array(\@base_sols);
        }
        return _array();
    }

    state $b2 = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_mul($b2, $b_z, $b_z);
    Math::GMPz::Rmpz_sub($D_z, $D_z, $b2);

    # Allow any non-parabolic form (D != 0)
    if (Math::GMPz::Rmpz_sgn($D_z) != 0) {
        my $scaled_n = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul($scaled_n, $a_z, $n_z);
        Math::GMPz::Rmpz_mul_ui($scaled_n, $scaled_n, 4);

        my $sols = (bless \$D_z)->solve_quadratic_form(bless \$scaled_n);
        my @final_sols;

        state $curr_X    = Math::GMPz::Rmpz_init_nobless();
        state $curr_Y    = Math::GMPz::Rmpz_init_nobless();
        state $numerator = Math::GMPz::Rmpz_init_nobless();
        state $by        = Math::GMPz::Rmpz_init_nobless();
        state $two_a     = Math::GMPz::Rmpz_init_nobless();

        foreach my $sol (@$sols) {
            my $X = _any2mpz(${$sol->[0]});
            my $Y = _any2mpz(${$sol->[1]});

            # Test permutations of signs for X and Y
            for my $sx (1, -1) {
                for my $sy (1, -1) {
                    Math::GMPz::Rmpz_set($curr_X, $X);
                    Math::GMPz::Rmpz_set($curr_Y, $Y);
                    Math::GMPz::Rmpz_neg($curr_X, $curr_X) if $sx < 0;
                    Math::GMPz::Rmpz_neg($curr_Y, $curr_Y) if $sy < 0;

                    # We need 2ax = X - bY
                    Math::GMPz::Rmpz_set($numerator, $curr_X);
                    Math::GMPz::Rmpz_mul($by, $b_z, $curr_Y);
                    Math::GMPz::Rmpz_sub($numerator, $numerator, $by);
                    Math::GMPz::Rmpz_mul_ui($two_a, $a_z, 2);

                    if (Math::GMPz::Rmpz_divisible_p($numerator, $two_a)) {
                        my $x_val = Math::GMPz::Rmpz_init();
                        my $y_val = Math::GMPz::Rmpz_init();
                        Math::GMPz::Rmpz_divexact($x_val, $numerator, $two_a);
                        Math::GMPz::Rmpz_set($y_val, $curr_Y);
                        push @final_sols, _array([(bless \$x_val), (bless \$y_val)]);
                    }
                }
            }
        }
        if (@final_sols) {
            my %seen;
            @final_sols = grep { !$seen{"${$_->[0]};${$_->[1]}"}++ } @final_sols;
            @final_sols = sort { ${$a->[0]} <=> ${$b->[0]} || ${$a->[1]} <=> ${$b->[1]} } @final_sols;
            return _array(\@final_sols);
        }
    }

    return _array();
}

sub solve_ternary_quadratic_form {
    my ($A, $B, $C, $n) = @_;

    ref($A) eq __PACKAGE__ or _valid(\$A);
    ref($B) eq __PACKAGE__ or _valid(\$B);
    ref($C) eq __PACKAGE__ or _valid(\$C);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    my $a_z = _any2mpz($$A) // return _array();
    my $b_z = _any2mpz($$B) // return _array();
    my $c_z = _any2mpz($$C) // return _array();
    my $n_z = _any2mpz($$n) // return _array();

    my $a_sgn = Math::GMPz::Rmpz_sgn($a_z);
    my $b_sgn = Math::GMPz::Rmpz_sgn($b_z);
    my $c_sgn = Math::GMPz::Rmpz_sgn($c_z);

    # Reject degenerate forms where a coefficient is zero: this is no
    # longer a (nondegenerate) ternary quadratic form.
    if ($a_sgn == 0 || $b_sgn == 0 || $c_sgn == 0) {
        return _array();
    }

    if ($a_sgn < 0 && $b_sgn < 0 && $c_sgn < 0) {

        # Negative definite form: a*x^2 + b*y^2 + c*z^2 = n has exactly
        # the same solutions (x,y,z) as (-a)*x^2 + (-b)*y^2 + (-c)*z^2 = -n,
        # which is positive definite. Flip signs once here and let the
        # rest of the routine handle it exactly like any other
        # positive-definite input.
        my $pa = Math::GMPz::Rmpz_init();
        my $pb = Math::GMPz::Rmpz_init();
        my $pc = Math::GMPz::Rmpz_init();
        my $pn = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_neg($pa, $a_z);
        Math::GMPz::Rmpz_neg($pb, $b_z);
        Math::GMPz::Rmpz_neg($pc, $c_z);
        Math::GMPz::Rmpz_neg($pn, $n_z);

        ($a_z, $b_z, $c_z, $n_z) = ($pa, $pb, $pc, $pn);
    }
    elsif ($a_sgn < 0 || $b_sgn < 0 || $c_sgn < 0) {

        # Mixed-sign (indefinite) ternary form. Unlike the definite case,
        # an indefinite form can represent a given n with infinitely many
        # solutions (e.g. x^2 + y^2 - z^2 = 1 has a solution for every z),
        # so there is no bound to search up to and no finite list to
        # return in general. This solver only handles definite forms.
        return _array();
    }

    # From here on, $a_z, $b_z, $c_z are all strictly positive (either
    # they started that way, or were just made that way above), so
    # everything below is exactly the original positive-definite solver.

    if (Math::GMPz::Rmpz_sgn($n_z) < 0) {
        return _array();
    }

    my @solutions;
    my %seen;

    my $store_solution = sub {
        my ($x, $y, $z) = @_;

        if (Math::GMPz::Rmpz_cmp($a_z, $b_z) == 0) {
            if (Math::GMPz::Rmpz_cmp($a_z, $c_z) == 0) {
                ($x, $y, $z) = sort { Math::GMPz::Rmpz_cmp($a, $b) } ($x, $y, $z);
            }
            elsif (Math::GMPz::Rmpz_cmp($x, $y) > 0) {
                ($x, $y) = ($y, $x);
            }
        }
        elsif (Math::GMPz::Rmpz_cmp($b_z, $c_z) == 0) {
            if (Math::GMPz::Rmpz_cmp($y, $z) > 0) {
                ($y, $z) = ($z, $y);
            }
        }
        elsif (Math::GMPz::Rmpz_cmp($a_z, $c_z) == 0) {
            if (Math::GMPz::Rmpz_cmp($x, $z) > 0) {
                ($x, $z) = ($z, $x);
            }
        }

        my $key = join(';', Math::GMPz::Rmpz_get_str($x, 16), Math::GMPz::Rmpz_get_str($y, 16), Math::GMPz::Rmpz_get_str($z, 16));

        if (!$seen{$key}++) {
            push @solutions, _array([bless(\$x), bless(\$y), bless(\$z)]);
        }
    };

    my $sort_solutions = sub {
        @solutions = sort { ${$a->[0]} <=> ${$b->[0]} || ${$a->[1]} <=> ${$b->[1]} || ${$a->[2]} <=> ${$b->[2]} } @solutions;
    };

    # FAST PATH: PARI/GP executes the entire loop internally to avoid pipe latency
    if ($USE_PARI_GP and Math::GMPz::Rmpz_sizeinbase($n_z, 2) >= 20) {
        my $a_str = Math::GMPz::Rmpz_get_str($a_z, 10);
        my $b_str = Math::GMPz::Rmpz_get_str($b_z, 10);
        my $c_str = Math::GMPz::Rmpz_get_str($c_z, 10);
        my $n_str = Math::GMPz::Rmpz_get_str($n_z, 10);

        my $code = <<"GP" =~ s/\n/ /gr;
iferr(
    my(res=List());
    my(a=$a_str, b=$b_str, c=$c_str, n=$n_str);
    my(z_limit=sqrtint(n\\c));
    for(z=0, z_limit,
        my(rem = n - c*z^2);
        my(v = qfbsolve(Qfb(a,0,b), rem, 3));
        if (#v,
            for(i=1, #v,
                listput(res, [abs(v[i][1]), abs(v[i][2]), z]);
            )
        );
    );
    Vec(res),
E, "")
GP

        if (my $res = _execute_pari_gp($code)) {
            return _array() if $res eq '';

            while ($res =~ /\[(-?\d+),\s*(-?\d+),\s*(-?\d+)\]/g) {

                my $x_val = Math::GMPz::Rmpz_init_set_str($1, 10);
                my $y_val = Math::GMPz::Rmpz_init_set_str($2, 10);
                my $z_val = Math::GMPz::Rmpz_init_set_str($3, 10);

                $store_solution->($x_val, $y_val, $z_val);
            }
            if (@solutions) {
                $sort_solutions->();
                return _array(\@solutions);
            }
        }
    }

    # FALLBACK: Pure-Perl loop using Cornacchia reduction
    my $z_limit = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_tdiv_q($z_limit, $n_z, $c_z);
    Math::GMPz::Rmpz_sqrt($z_limit, $z_limit);

    my $z_mpz = Math::GMPz::Rmpz_init_set_ui(0);
    my $rem   = Math::GMPz::Rmpz_init();
    my $cz2   = Math::GMPz::Rmpz_init();

    my $D_z = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($D_z, $a_z, $b_z);
    my $D_obj = bless \$D_z;

    my $M_z   = Math::GMPz::Rmpz_init();
    my $X_mpz = Math::GMPz::Rmpz_init();

    while (Math::GMPz::Rmpz_cmp($z_mpz, $z_limit) <= 0) {

        Math::GMPz::Rmpz_mul($cz2, $z_mpz, $z_mpz);
        Math::GMPz::Rmpz_mul($cz2, $cz2,   $c_z);
        Math::GMPz::Rmpz_sub($rem, $n_z, $cz2);

        if (Math::GMPz::Rmpz_sgn($rem) == 0) {
            my $key = join(';', '0', '0', Math::GMPz::Rmpz_get_str($z_mpz, 16));
            if (!$seen{$key}++) {
                my $z_copy = Math::GMPz::Rmpz_init_set($z_mpz);
                push @solutions, _array([ZERO, ZERO, bless(\$z_copy)]);
            }
        }
        else {
            Math::GMPz::Rmpz_mul($M_z, $a_z, $rem);
            my $M_obj = bless \$M_z;

            # Call existing binary quadratic form solver
            my $sols = $D_obj->solve_quadratic_form($M_obj);

            foreach my $sol (@$sols) {
                my $X_val = Math::GMPz::Rmpz_init_set(_any2mpz(${$sol->[0]}));

                # We need x = X / a to be a perfect integer
                if (Math::GMPz::Rmpz_divisible_p($X_val, $a_z)) {

                    Math::GMPz::Rmpz_divexact($X_mpz, $X_val, $a_z);

                    my $x_copy = Math::GMPz::Rmpz_init_set($X_mpz);
                    my $y_copy = Math::GMPz::Rmpz_init_set(_any2mpz(${$sol->[1]}));
                    my $z_copy = Math::GMPz::Rmpz_init_set($z_mpz);

                    $store_solution->($x_copy, $y_copy, $z_copy);
                }
            }
        }
        Math::GMPz::Rmpz_add_ui($z_mpz, $z_mpz, 1);
    }

    $sort_solutions->();
    _array(\@solutions);
}

sub geometric_sum {
    my ($n, $r) = @_;
    _valid(\$r);

    # (r^(n+1) - 1) / (r-1)

    $n = $$n;
    $r = $$r;

    bless \__div__(__sub__(__pow__($r, __add__($n, 1)), 1), __sub__($r, 1));
}

sub geometric_summod {
    my ($n, $r, $m) = @_;
    _valid(\$r, \$m);

    # (r^(n+1) - 1) * invmod(r-1, m) (mod m)

    $n = _any2mpz($$n) // goto &nan;
    $r = _any2mpz($$r) // goto &nan;
    $m = _any2mpz($$m) // goto &nan;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_add_ui($t, $n, 1);

    if (Math::GMPz::Rmpz_sgn($t) < 0) {
        Math::GMPz::Rmpz_invert($u, $r, $m) || goto &nan;
    }

    Math::GMPz::Rmpz_powm($t, $r, $t, $m);
    Math::GMPz::Rmpz_sub_ui($t, $t, 1);
    Math::GMPz::Rmpz_sub_ui($u, $r, 1);
    Math::GMPz::Rmpz_invert($u, $u, $m) || goto &nan;
    Math::GMPz::Rmpz_mul($t, $t, $u);
    Math::GMPz::Rmpz_mod($t, $t, $m);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($t)
      ? Math::GMPz::Rmpz_get_ui($t)
      : Math::GMPz::Rmpz_init_set($t);
    bless \$r2;
}

sub faulhaber_range {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    return ZERO if $to->lt($from);
    return $to->faulhaber_sum($k)->sub($from->dec->faulhaber_sum($k));
}

sub power_sum {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->power_sum($to)->sub($k->power_sum($from->dec));
    }

    $from->iroot($k)->faulhaber_sum($k);
}

sub power_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->power_count($to)->sub($k->power_count($from->dec));
    }

    $from->iroot($k);
}

sub faulhaber_sum {
    my ($n, $p) = @_;

    _valid(\$p);

    $p = $$p;

    if (ref($p)) {
        $p = _any2ui($p) // goto &nan;
    }

    if ($p == 0) {
        return $n->max(ZERO);
    }
    elsif ($p < 0) {
        goto &nan;
    }

    $n = $$n;

    if (FAST_MODE and !ref($n) and $n >= 0) {
        if (HAS_PRIME_UTIL and $n**($p + 1) < ULONG_MAX) {
            return _set_int(Math::Prime::Util::powersum($n, $p));
        }
        return _set_int(Math::Prime::Util::GMP::powersum($n, $p));
    }

    $n = _any2mpz($n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    if ($p == 1 or $p == 3) {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_add_ui($r, $n, 1);
        Math::GMPz::Rmpz_mul($r, $r, $n);
        Math::GMPz::Rmpz_div_2exp($r, $r, 1);
        Math::GMPz::Rmpz_mul($r, $r, $r) if ($p == 3);
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    state $z = Math::GMPz::Rmpz_init_nobless();

    if ($p == 2) {    # n*(n+1)*(2*n+1)/6
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_add_ui($z, $n, 1);
        Math::GMPz::Rmpz_mul($r, $z, $n);
        Math::GMPz::Rmpz_mul_2exp($z, $z, 1);
        Math::GMPz::Rmpz_sub_ui($z, $z, 1);
        Math::GMPz::Rmpz_mul($r, $r, $z);
        Math::GMPz::Rmpz_divexact_ui($r, $r, 6);
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    # When p >= n, sum the powers directly.
    if (Math::GMPz::Rmpz_cmp_ui($n, $p) <= 0) {
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_ui($r, 0);
        foreach my $k (1 .. Math::GMPz::Rmpz_get_ui($n)) {
            Math::GMPz::Rmpz_ui_pow_ui($z, $k, $p);
            Math::GMPz::Rmpz_add($r, $r, $z);
        }
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    my @B = _bernoulli_numbers($p);

    state $u = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($u, 1);

    my $q   = Math::GMPq::Rmpq_init();
    my $sum = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_ui($sum, 0, 1);

    # Sum_{k=1..n} k^p = 1/(p+1) * Sum_{j=0..p} binomial(p+1, j) * n^(p-j+1) * bernoulli(j)
    #                  = 1/(p+1) * Sum_{j=0..p} binomial(p+1, p-j) * n^(j+1) * bernoulli(p-j)

    foreach my $j (0 .. $p - 2) {

        Math::GMPz::Rmpz_mul($u, $u, $n);

        # Skip when bernoulli(p-j) == 0
        if (($p - $j) % 2 == 0) {
            Math::GMPz::Rmpz_bin_uiui($z, $p + 1, $j + 1);
            Math::GMPz::Rmpz_mul($z, $z, $u);
            Math::GMPq::Rmpq_mul_z($q, $B[(($p - $j) >> 1) + 1], $z);
            Math::GMPq::Rmpq_add($sum, $sum, $q);
        }
    }

    # sum += (1/2) * n^p * (2*n + p + 1)
    Math::GMPz::Rmpz_mul($u, $u, $n);
    Math::GMPz::Rmpz_mul_2exp($z, $n, 1);
    Math::GMPz::Rmpz_add_ui($z, $z, $p + 1);
    Math::GMPz::Rmpz_mul($u, $u, $z);
    Math::GMPq::Rmpq_set_ui($q, 1, 2);
    Math::GMPq::Rmpq_mul_z($q, $q, $u);
    Math::GMPq::Rmpq_add($sum, $sum, $q);

    # z = sum/(p+1)
    Math::GMPq::Rmpq_get_num($u, $sum);
    Math::GMPz::Rmpz_divexact_ui($u, $u, $p + 1);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($u)
      ? Math::GMPz::Rmpz_get_ui($u)
      : Math::GMPz::Rmpz_init_set($u);
    bless \$r2;
}

*faulhaber    = \&faulhaber_sum;
*Faulhaber    = \&faulhaber_sum;
*FaulhaberSum = \&faulhaber_sum;

sub multinomial {
    my ($n, @mset) = @_;

    $n = _any2mpz($$n) // goto &nan;

    my $bin  = Math::GMPz::Rmpz_init();
    my $sum  = Math::GMPz::Rmpz_init_set($n);
    my $prod = Math::GMPz::Rmpz_init_set_ui(1);

    foreach my $k (@mset) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = _any2si($$k) // goto &nan;

        $k < 0
          ? Math::GMPz::Rmpz_sub_ui($sum, $sum, -$k)
          : Math::GMPz::Rmpz_add_ui($sum, $sum, $k);

        $k < 0
          ? Math::GMPz::Rmpz_bin_si($bin, $sum, $k)
          : Math::GMPz::Rmpz_bin_ui($bin, $sum, $k);

        Math::GMPz::Rmpz_mul($prod, $prod, $bin);
    }

    bless \$prod;
}

sub catalan {
    my ($n, $k) = @_;

    # Catalan triangle
    # catalan(n, k) = binomial(n+k, k) - binomial(n+k, k-1)
    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $n = _any2mpz($$n) // goto &nan;
        $k = _any2ui($$k)  // goto &nan;

        my $t = Math::GMPz::Rmpz_init();
        my $u = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_add_ui($t, $n, $k);
        Math::GMPz::Rmpz_bin_ui($u, $t, $k);
        ($k > 0)
          ? Math::GMPz::Rmpz_bin_ui($t, $t, $k - 1)
          : Math::GMPz::Rmpz_bin_si($t, $t, $k - 1);
        Math::GMPz::Rmpz_sub($u, $u, $t);

        return bless \$u;
    }

    $n = _any2ui($$n) // goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_bin_uiui($r, $n << 1, $n);
    Math::GMPz::Rmpz_divexact_ui($r, $r, $n + 1);
    bless \$r;
}

*Catalan = \&catalan;

sub necklaces {
    my ($n, $k) = @_;

    # (1/n)*sum_{d|n} phi(n/d)*k^d

    $n->is_zero && return ONE;
    $n->dirichlet_convolution(
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->euler_phi;
            }
        ),
        Sidef::Types::Block::Block->new(
            code => sub {
                $k->ipow($_[0]);
            }
        )
    )->idiv($n);
}

sub necklaces_aperiodic {
    my ($n, $k) = @_;

    # (1/n) * sum_{d|n} mu(d) * k^(n/d)

    $n->is_zero && return ONE;
    $n->dirichlet_convolution(
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->moebius;
            }
        ),
        Sidef::Types::Block::Block->new(
            code => sub {
                $k->ipow($_[0]);
            }
        )
    )->idiv($n);
}

sub binomial {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if (FAST_MODE and !ref($x) and $x <= 1e6 and $x >= 0 and $y >= 0) {

        if (HAS_PRIME_UTIL and $x <= ((INTSIZE == 32) ? 33 : 66)) {
            my $r = Math::Prime::Util::binomial($x, $y);
            return bless \$r;
        }

        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_bin_uiui($r, $x, $y);
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    $x = _any2mpz($x) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();

    ($y < 0)
      ? Math::GMPz::Rmpz_bin_si($r, $x, $y)
      : Math::GMPz::Rmpz_bin_ui($r, $x, $y);

    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);

    bless \$r2;
}

*nok = \&binomial;

sub eulerian_number {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2ui($$n) // goto &nan;
    $k = _any2ui($$k) // goto &nan;

    if ($k >= $n || $n == 0) {
        return ($k == 0 && $n == 0) ? ONE : ZERO;
    }

    # Inclusion-Exclusion Loop
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);
    state $term = Math::GMPz::Rmpz_init_nobless();
    state $bin  = Math::GMPz::Rmpz_init_nobless();

    for my $j (0 .. $k + 1) {
        Math::GMPz::Rmpz_bin_uiui($bin, $n + 1, $j);
        Math::GMPz::Rmpz_ui_pow_ui($term, $k + 1 - $j, $n);
        Math::GMPz::Rmpz_mul($term, $term, $bin);

        if ($j % 2 == 1) {
            Math::GMPz::Rmpz_sub($sum, $sum, $term);
        }
        else {
            Math::GMPz::Rmpz_add($sum, $sum, $term);
        }
    }

    return bless \$sum;
}

*eulerian = \&eulerian_number;

# Multiply two polynomials modulo p^e, truncating safely to degree e-1
sub _poly_mul {
    my ($A, $B, $pk, $e) = @_;
    my @C = (0) x $e;
    for my $i (0 .. $e - 1) {
        next unless $A->[$i];
        for my $j (0 .. $e - $i - 1) {
            next unless $B->[$j];
            $C[$i + $j] =
              (HAS_PRIME_UTIL and $pk < ULONG_MAX)
              ? Math::Prime::Util::muladdmod($A->[$i], $B->[$j], $C[$i + $j], $pk)
              : Math::Prime::Util::GMP::muladdmod($A->[$i], $B->[$j], $C[$i + $j], $pk);
        }
    }
    return \@C;
}

# Shift a polynomial B(x) = A(x + h) modulo p^e
sub _poly_shift {
    my ($A, $h_str, $pk, $e) = @_;
    my @B = (0) x $e;
    for my $j (0 .. $e - 1) {
        next unless $A->[$j];
        my $h_pow = 1;
        for (my $m = $j ; $m >= 0 ; --$m) {
            my $bin =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::binomial($j, $m)
              : Math::Prime::Util::GMP::binomial($j, $m);
            my $term =
              (HAS_PRIME_UTIL and $pk < ULONG_MAX)
              ? Math::Prime::Util::mulmod($bin, $h_pow, $pk)
              : Math::Prime::Util::GMP::mulmod($bin, $h_pow, $pk);
            $B[$m] =
              (HAS_PRIME_UTIL and $pk < ULONG_MAX)
              ? Math::Prime::Util::muladdmod($term, $A->[$j], $B[$m], $pk)
              : Math::Prime::Util::GMP::muladdmod($term, $A->[$j], $B[$m], $pk);

            if ($m > 0) {
                $h_pow =
                  (HAS_PRIME_UTIL and $pk < ULONG_MAX)
                  ? Math::Prime::Util::mulmod($h_pow, $h_str, $pk)
                  : Math::Prime::Util::GMP::mulmod($h_pow, $h_str, $pk);
            }
        }
    }
    return \@B;
}

# Divide and conquer recursive evaluation of P(x, q)
sub _build_polynomial {
    my ($q_str, $poly, $pk, $e) = @_;

    if ($q_str eq "0") {
        my @res = (0) x $e;
        $res[0] = 1;
        return \@res;
    }
    if ($q_str eq "1") {
        return $poly;
    }

    my $h_str = Math::Prime::Util::GMP::rshiftint($q_str, 1);    # floor(q/2)

    my $P_h         = __SUB__->($h_str, $poly, $pk, $e);
    my $P_h_shifted = _poly_shift($P_h, $h_str, $pk, $e);
    my $P_2h        = _poly_mul($P_h, $P_h_shifted, $pk, $e);

    if (Math::Prime::Util::GMP::modint($q_str, 2) == 1) {
        my $two_h_str    = Math::Prime::Util::GMP::lshiftint($h_str, 1);
        my $poly_shifted = _poly_shift($poly, $two_h_str, $pk, $e);
        return _poly_mul($P_2h, $poly_shifted, $pk, $e);
    }

    return $P_2h;
}

sub _factorial_without_prime_pe {
    my ($n, $p, $e, $pk) = @_;

    if (Math::Prime::Util::GMP::cmpint($p, $n) > 0) {
        return (
                (HAS_PRIME_UTIL and $pk < ULONG_MAX)
                ? Math::Prime::Util::factorialmod($n, $pk)
                : Math::Prime::Util::GMP::factorialmod($n, $pk)
               );
    }

    my $fact = 1;
    my @c    = (1, (0) x ($e - 1));

    for my $j (1 .. $p - 1) {

        $fact = Math::Prime::Util::GMP::mulmod($fact, $j, $pk);
        my $inv = Math::Prime::Util::GMP::invmod($j, $pk);

        for (my $k = $e - 1 ; $k >= 1 ; --$k) {
            if ($c[$k - 1]) {
                $c[$k] = Math::Prime::Util::GMP::muladdmod($c[$k - 1], $inv, $c[$k], $pk);
            }
        }
    }

    my @poly;
    my $p_pow = 1;
    for my $k (0 .. $e - 1) {
        my $coeff = Math::Prime::Util::GMP::mulmod($c[$k], $fact, $pk);
        push @poly, Math::Prime::Util::GMP::mulmod($coeff, $p_pow, $pk);
        $p_pow = Math::Prime::Util::GMP::mulmod($p_pow, $p, $pk);
    }

    my $q = Math::Prime::Util::GMP::divint($n, $p);
    my $r = Math::Prime::Util::GMP::modint($n, $p);

    my $P_q = _build_polynomial($q, \@poly, $pk, $e);
    my $res = $P_q->[0];

    my $pq = Math::Prime::Util::GMP::mulint($q, $p);
    if ($r > 0) {
        for my $j (1 .. $r) {
            $res =
              (HAS_PRIME_UTIL and $pk < ULONG_MAX)
              ? Math::Prime::Util::mulmod($res, Math::Prime::Util::addint($pq, $j), $pk)
              : Math::Prime::Util::GMP::mulmod($res, Math::Prime::Util::GMP::addint($pq, $j), $pk);
        }
    }

    return $res;
}

sub _factorial_without_prime {
    my ($n, $p, $e, $pk, $from, $res) = @_;

    return 1 if (Math::Prime::Util::GMP::cmpint($n, 1) <= 0);

    if (Math::Prime::Util::GMP::cmpint($p, $n) > 0) {
        return (
                (HAS_PRIME_UTIL and $pk < ULONG_MAX)
                ? Math::Prime::Util::factorialmod($n, $pk)
                : Math::Prime::Util::GMP::factorialmod($n, $pk)
               );
    }

    if (Math::Prime::Util::GMP::cmpint($$from, $n) == 0) {
        return $$res;
    }

    if (Math::Prime::Util::GMP::cmpint($$from, $n) > 0) {
        $$from = 0;
        $$res  = 1;
    }

    my $q2_pk = Math::Prime::Util::GMP::mulint($p, $p);
    if ($p > 2 && Math::Prime::Util::GMP::cmpint($pk, $q2_pk) == 0) {
        my $A = Math::Prime::Util::GMP::divint($n, $p);
        my $B = Math::Prime::Util::GMP::modint($n, $p);

        my $pm1_fact =
          (HAS_PRIME_UTIL and $pk < ULONG_MAX)
          ? Math::Prime::Util::factorialmod(Math::Prime::Util::subint($p, 1), $pk)
          : Math::Prime::Util::GMP::factorialmod(Math::Prime::Util::GMP::subint($p, 1), $pk);

        my $b_fact =
          (HAS_PRIME_UTIL and $pk < ULONG_MAX)
          ? Math::Prime::Util::factorialmod($B, $pk)
          : Math::Prime::Util::GMP::factorialmod($B, $pk);

        my $Hb = 0;
        for my $j (1 .. $B) {
            $Hb =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::addmod($Hb, Math::Prime::Util::invmod($j, $p), $p)
              : Math::Prime::Util::GMP::addmod($Hb, Math::Prime::Util::GMP::invmod($j, $p), $p);
        }

        my $res_q2 = Math::Prime::Util::GMP::powmod($pm1_fact, $A, $pk);
        $res_q2 = Math::Prime::Util::GMP::mulmod($res_q2, $b_fact, $pk);

        if ($A > 0 && $Hb > 0) {
            my $ap   = Math::Prime::Util::GMP::mulmod($A,  $p,  $pk);
            my $apHb = Math::Prime::Util::GMP::mulmod($ap, $Hb, $pk);
            my $term = Math::Prime::Util::GMP::addmod(1, $apHb, $pk);
            $res_q2 = Math::Prime::Util::GMP::mulmod($res_q2, $term, $pk);
        }

        $$res  = $res_q2;
        $$from = $n;
        return $res_q2;
    }

    my $r_pe = _factorial_without_prime_pe($n, $p, $e, $pk);

    $$res  = $r_pe;
    $$from = $n;
    return $r_pe;
}

sub _small_k_binomialmod {
    my ($n_val, $k_val, $m_val, $p) = @_;

    $n_val = Math::GMPz::Rmpz_init_set_str($n_val, 10) if !ref($n_val);
    $m_val = Math::GMPz::Rmpz_init_set_str($m_val, 10) if !ref($m_val);

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n_val) and Math::GMPz::Rmpz_cmpabs_ui($m_val, 1e7) <= 0) {
        return Math::Prime::Util::binomialmod($n_val, $k_val, $m_val);
    }

    if (!$p and (my $exp = Math::Prime::Util::GMP::is_prime_power($m_val))) {
        $p = Math::Prime::Util::GMP::rootint($m_val, $exp);
    }

    if (!$p or $k_val <= 1e4) {
        my $bin = Math::GMPz::Rmpz_init();
        if (Math::GMPz::Rmpz_cmp_ui($n_val, 1e5) <= 0) {
            Math::GMPz::Rmpz_bin_uiui($bin, Math::GMPz::Rmpz_get_ui($n_val), $k_val);
        }
        else {
            Math::GMPz::Rmpz_bin_ui($bin, $n_val, $k_val);
        }
        Math::GMPz::Rmpz_mod($bin, $bin, $m_val);
        return $bin;
    }

    my $v = 0;
    state $num_mult = Math::GMPz::Rmpz_init_nobless();
    state $den_mult = Math::GMPz::Rmpz_init_nobless();
    state $temp     = Math::GMPz::Rmpz_init_nobless();
    state $nmi      = Math::GMPz::Rmpz_init_nobless();
    state $p_z      = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($num_mult, 1);
    Math::GMPz::Rmpz_set_ui($den_mult, 1);
    Math::GMPz::Rmpz_set_ui($p_z,      $p);

    Math::GMPz::Rmpz_set($nmi, $n_val);

    for my $i (0 .. $k_val - 1) {

        if (Math::GMPz::Rmpz_divisible_ui_p($nmi, $p)) {
            $v += Math::GMPz::Rmpz_remove($temp, $nmi, $p_z);
            Math::GMPz::Rmpz_mul($num_mult, $num_mult, $temp);
        }
        else {
            Math::GMPz::Rmpz_mul($num_mult, $num_mult, $nmi);
        }

        Math::GMPz::Rmpz_mod($num_mult, $num_mult, $m_val);

        my $den = $i + 1;
        while ($den % $p == 0) {
            $den =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divint($den, $p)
              : Math::Prime::Util::GMP::divint($den, $p);
            $v -= 1;
        }

        Math::GMPz::Rmpz_mul_ui($den_mult, $den_mult, $den);
        Math::GMPz::Rmpz_mod($den_mult, $den_mult, $m_val);
        Math::GMPz::Rmpz_sub_ui($nmi, $nmi, 1);
    }

    Math::GMPz::Rmpz_invert($temp, $den_mult, $m_val);

    my $ans = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($ans, $num_mult, $temp);
    Math::GMPz::Rmpz_mod($ans, $ans, $m_val);

    if ($v > 0) {
        Math::GMPz::Rmpz_powm_ui($temp, $p_z, $v, $m_val);
        Math::GMPz::Rmpz_mul($ans, $ans, $temp);
        Math::GMPz::Rmpz_mod($ans, $ans, $m_val);
    }

    return $ans;
}

sub _is_small_k_binomialmod {
    my ($n, $k, $m) = @_;

    $n >= 1e6 or return;

    if ($m >= 1e7 and $n >= 1e7 and $k <= 1e6) {
        return 1;
    }

    my $new_k = Math::Prime::Util::GMP::subint($n, $k);

    if ($new_k > 0 and $new_k < $k) {
        $k = $new_k;
    }

    $k <= 1e7 or return;

    my $sqrt_m   = Math::Prime::Util::GMP::sqrtint($m);
    my $m_over_n = Math::Prime::Util::GMP::divint($m, $n);

    $k < $sqrt_m and $k < $m_over_n;
}

sub _lucas_theorem {    # p is prime
    my ($n, $k, $p) = @_;

    my (@nd, @kd);
    while ($k) {
        my $np = Math::Prime::Util::GMP::modint($n, $p);
        my $kp = Math::Prime::Util::GMP::modint($k, $p);

        if ($kp > 0) {
            push @nd, $np;
            push @kd, $kp;
        }

        if ($kp > $np) { return 0 }
        $n = Math::Prime::Util::GMP::divint($n, $p);
        $k = Math::Prime::Util::GMP::divint($k, $p);
    }

    my $r = 1;
    foreach my $i (0 .. $#nd) {
        my $np = $nd[$i];
        my $kp = $kd[$i];
        my $rp = Math::Prime::Util::GMP::subint($np, $kp);

        if (_is_small_k_binomialmod($np, $kp, $p)) {
            my $bin = _small_k_binomialmod($np, $kp, $p, $p);
            $r = Math::Prime::Util::GMP::mulmod($r, $bin, $p);
        }
        else {
            my $x = Math::Prime::Util::GMP::factorialmod($np, $p);
            my $y = Math::Prime::Util::GMP::factorialmod($kp, $p);
            my $z = Math::Prime::Util::GMP::factorialmod($rp, $p);

            $y = Math::Prime::Util::GMP::mulmod($y, $z, $p);
            $x = Math::Prime::Util::GMP::divmod($x, $y, $p) if ($y ne '1');
            $r = Math::Prime::Util::GMP::mulmod($r, $x, $p);
        }
    }

    return $r;
}

sub _modular_binomial {
    my ($n, $k, $m) = @_;

    # Translation of binomod.gp v1.5 by Max Alekseyev, with some extra optimizations.

    if (Math::GMPz::Rmpz_cmp_ui($m, 1) == 0) {
        return 0;
    }

    if (Math::GMPz::Rmpz_sgn($k) < 0) {
        $k = $n - $k;
    }

    if (Math::GMPz::Rmpz_sgn($k) < 0) {
        return 0;
    }

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        my $x = Math::GMPz::Rmpz_even_p($k) ? 1 : -1;
        $x = Math::Prime::Util::GMP::mulint($x, __SUB__->(-$n + $k - 1, $k, $m));
        return $x;
    }

    if (Math::GMPz::Rmpz_cmp($k, $n) > 0) {
        return 0;
    }

    if (Math::GMPz::Rmpz_sgn($k) == 0 or Math::GMPz::Rmpz_cmp($k, $n) == 0) {
        return 1;
    }

    if (Math::GMPz::Rmpz_cmp_ui($k, 1) == 0 or $k == $n - 1) {
        return $n;
    }

    if (Math::GMPz::Rmpz_cmp($n - $k, $k) < 0) {
        $k = $n - $k;
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n) and Math::GMPz::Rmpz_cmpabs_ui($m, 1e7) <= 0) {
        return Math::Prime::Util::binomialmod($n, $k, $m);
    }

    if (Math::GMPz::Rmpz_cmp_ui($k, 1e4) <= 0) {
        return _small_k_binomialmod($n, $k, $m);
    }

    my @F;

    foreach my $pp (_factor_exp(Math::Prime::Util::GMP::absint($m))) {
        my ($p, $q) = @$pp;

        if ($q == 1) {
            if (HAS_PRIME_UTIL and $n < ULONG_MAX and $p < ULONG_MAX) {
                push @F, [Math::Prime::Util::binomialmod($n, $k, $p), $p];
            }
            else {
                push @F, [_lucas_theorem($n, $k, $p), $p];
            }
            next;
        }

        my $pq = Math::Prime::Util::GMP::powint($p, $q);

        if (Math::Prime::Util::GMP::cmpint($p, $n) > 0) {
            push @F, [_small_k_binomialmod($n, $k, $pq, $p), $pq];
            next;
        }

        my $d = __ilog__($n, $p) + 1;

        my (@np, @kp);

        {
            my $pi = 1;
            foreach my $i (0 .. $d) {
                push @np, Math::Prime::Util::GMP::modint(Math::Prime::Util::GMP::divint($n, $pi), $p);
                push @kp, Math::Prime::Util::GMP::modint(Math::Prime::Util::GMP::divint($k, $pi), $p);
                $pi = Math::Prime::Util::GMP::mulint($pi, $p);
            }
        }

        my @e;

        foreach my $i (0 .. $d) {
            $e[$i] = ($np[$i] < ($kp[$i] + (($i > 0) ? $e[$i - 1] : 0))) ? 1 : 0;
        }

        for (my $i = $d - 1 ; $i >= 0 ; --$i) {
            $e[$i] += $e[$i + 1];
        }

        if ($e[0] >= $q) {
            push @F, [0, Math::Prime::Util::GMP::powint($p, $q)];
            next;
        }

        if (_is_small_k_binomialmod($n, $k, Math::Prime::Util::GMP::mulint($p, $q))) {
            my $bin = _small_k_binomialmod($n, $k, $pq, $p);
            push @F, [$bin, $pq];
            next;
        }

        if (HAS_PRIME_UTIL and $n < ULONG_MAX and $pq < 1e7) {
            push @F, [Math::Prime::Util::binomialmod($n, $k, $pq), $pq];
            next;
        }

        my $rq  = $q - $e[0];
        my $prq = Math::Prime::Util::GMP::powint($p, $rq);

        my (@N, @K, @R);

        {
            my $pi = 1;
            my $r  = Math::Prime::Util::GMP::subint($n, $k);
            foreach my $i (0 .. $d) {
                push @N, Math::Prime::Util::GMP::modint(Math::Prime::Util::GMP::divint($n, $pi), $prq);
                push @K, Math::Prime::Util::GMP::modint(Math::Prime::Util::GMP::divint($k, $pi), $prq);
                push @R, Math::Prime::Util::GMP::modint(Math::Prime::Util::GMP::divint($r, $pi), $prq);
                $pi = Math::Prime::Util::GMP::mulint($pi, $p);
            }
        }

        my @NKR = (
                   sort { Math::Prime::Util::GMP::cmpint($a->[3], $b->[3]) }
                   map  { [$N[$_], $K[$_], $R[$_], Math::Prime::Util::GMP::vecsum($N[$_], $K[$_], $R[$_])] } 0 .. $#N
                  );

        @N = map { $_->[0] } @NKR;
        @K = map { $_->[1] } @NKR;
        @R = map { $_->[2] } @NKR;

        my %acc  = ('0' => 1);
        my $nfac = 1;

        if (FAST_MODE and $prq < ULONG_MAX and Math::Prime::Util::GMP::cmpint($p, $n) < 0) {
            foreach my $k (1 .. List::Util::min(List::Util::max(@N, @K, @R), 1e3)) {
                if ($k % $p) {
                    $nfac =
                      HAS_PRIME_UTIL
                      ? Math::Prime::Util::mulmod($nfac, $k, $prq)
                      : Math::Prime::Util::GMP::mulmod($nfac, $k, $prq);
                }
                $acc{$k} = $nfac;
            }
        }

        my $v = Math::Prime::Util::GMP::powmod($p, $e[0], $pq);

        {
            my $from = 0;
            my $res  = 1;

            foreach my $j (0 .. $d) {

                my @pairs;
                my ($x, $y, $z);

                ($x = $acc{$N[$j]}) // push(@pairs, [\$x, $N[$j]]);
                ($y = $acc{$K[$j]}) // push(@pairs, [\$y, $K[$j]]);
                ($z = $acc{$R[$j]}) // push(@pairs, [\$z, $R[$j]]);

                foreach my $pair (sort { Math::Prime::Util::GMP::cmpint($a->[1], $b->[1]) } @pairs) {
                    ${$pair->[0]} = _factorial_without_prime($pair->[1], $p, $rq, $prq, \$from, \$res);
                }

                $y = Math::Prime::Util::GMP::mulmod($y, $z, $pq);
                $x = Math::Prime::Util::GMP::divmod($x, $y, $pq) if ($y ne '1');
                $v = Math::Prime::Util::GMP::mulmod($v, $x, $pq);
            }
        }

        if ($p > 2 or $rq < 3) {
            $v = Math::Prime::Util::GMP::mulmod($v, ((($e[$rq - 1] // 0) % 2 == 0) ? 1 : -1), $pq);
        }

        push @F, [$v, $pq];
    }

    Math::Prime::Util::GMP::chinese(@F);
}

sub binomialmod {
    my ($n, $k, $m) = @_;

    _valid(\$k, \$m);

    $n = $$n;
    $k = $$k;
    $m = $$m;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($n) and !ref($k) and !ref($m) and CORE::abs($m) <= 1e7) {
        $m || goto &nan;
        my $r = Math::Prime::Util::GMP::modint(Math::Prime::Util::binomialmod($n, $k, $m), $m);
        return bless \$r;
    }

    $n = _any2mpz($n) // (goto &nan);
    $k = _any2mpz($k) // (goto &nan);
    $m = _any2mpz($m) // (goto &nan);

    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    _set_int(Math::Prime::Util::GMP::modint(_modular_binomial($n, $k, $m), $m));
}

sub totient_range {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _big2istr($$n) // return _array();
    $k = _big2istr($$k) // return _array();

    my @array = map { ref($_) eq __PACKAGE__ ? $_ : _set_int($_) } (
                                                                 (HAS_PRIME_UTIL and $k < ULONG_MAX)
                                                                 ? Math::Prime::Util::euler_phi($n, $k)
                                                                 : _set_int($n)->range(_set_int($k))->map(Sidef::Types::String::String->new("totient"))->to_list
    );

    return _array(\@array);
}

sub moebius_range {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _big2istr($$n) // return _array();
    $k = _big2istr($$k) // return _array();

    my @array = map { $_ ? ($_ == 1) ? ONE : MONE : ZERO } (
                                                            HAS_PRIME_UTIL
                                                            ? Math::Prime::Util::moebius($n, $k)
                                                            : Math::Prime::Util::GMP::moebius($n, $k)
                                                           );

    return _array(\@array);
}

*mobius_range = \&moebius_range;

sub moebius {
    my ($n) = @_;

    if (scalar(@_) > 1) {
        goto &moebius_range;
    }

    $n = $$n;

    my $m =
      (FAST_MODE and HAS_PRIME_UTIL and !ref($n) and $n >= 0)
      ? Math::Prime::Util::moebius($n)
      : Math::Prime::Util::GMP::moebius(_big2uistr($n) // goto &nan);

    $m ? ($m == 1) ? ONE : MONE : ZERO;
}

*μ       = \&moebius;
*mu      = \&moebius;
*mobius  = \&moebius;
*möbius  = \&moebius;
*Möbius  = \&moebius;
*Moebius = \&moebius;

sub mertens {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $x = _big2istr($$x) // return ZERO;
        $x = 1 if $x < 1;
        $y = _big2uistr($$y) // return ZERO;
    }
    else {
        $y = _big2uistr($$x) // return ZERO;
        $x = 1;
    }

    state $mertens_table = {

        # M(10^n), where M(x) is Mertens's function.
        # OEIS: https://oeis.org/A084237
        "10000000"                 => "1037",
        "100000000"                => "1928",
        "1000000000"               => "-222",
        "10000000000"              => "-33722",
        "100000000000"             => "-87856",
        "1000000000000"            => "62366",
        "10000000000000"           => "599582",
        "100000000000000"          => "-875575",
        "1000000000000000"         => "-3216373",
        "10000000000000000"        => "-3195437",
        "100000000000000000"       => "-21830254",
        "1000000000000000000"      => "-46758740",
        "10000000000000000000"     => "899990187",
        "100000000000000000000"    => "461113106",
        "1000000000000000000000"   => "-3395895277",
        "10000000000000000000000"  => "-2061910120",
        "100000000000000000000000" => "62467771689",

        # M(2^n), where M(n) is Mertens's function.
        # OEIS: https://oeis.org/A084236
        "1073741824"              => "-10374",
        "2147483648"              => "9569",
        "4294967296"              => "1814",
        "8589934592"              => "-10339",
        "17179869184"             => "-3421",
        "34359738368"             => "8435",
        "68719476736"             => "38176",
        "137438953472"            => "-28118",
        "274877906944"            => "38729",
        "549755813888"            => "-135944",
        "1099511627776"           => "101597",
        "2199023255552"           => "15295",
        "4398046511104"           => "-169338",
        "8796093022208"           => "259886",
        "17592186044416"          => "-474483",
        "35184372088832"          => "1726370",
        "70368744177664"          => "-3554573",
        "140737488355328"         => "-135443",
        "281474976710656"         => "3282200",
        "562949953421312"         => "1958235",
        "1125899906842624"        => "-1735147",
        "2251799813685248"        => "6657834",
        "4503599627370496"        => "-13927672",
        "9007199254740992"        => "-11901414",
        "18014398509481984"       => "48662015",
        "36028797018963968"       => "-48361472",
        "72057594037927936"       => "23952154",
        "144115188075855872"      => "51885062",
        "288230376151711744"      => "-15415164",
        "576460752303423488"      => "-89014828",
        "1152921504606846976"     => "-48425659",
        "2305843009213693952"     => "220660381",
        "4611686018427387904"     => "-248107163",
        "9223372036854775808"     => "580197744",
        "18446744073709551616"    => "-851764249",
        "36893488147419103232"    => "809210153",
        "73786976294838206464"    => "-1220538763",
        "147573952589676412928"   => "-925696220",
        "295147905179352825856"   => "2092394726",
        "590295810358705651712"   => "-3748189801",
        "1180591620717411303424"  => "9853266869",
        "2361183241434822606848"  => "-12658250658",
        "4722366482869645213696"  => "9558471405",
        "9444732965739290427392"  => "-6524408924",
        "18889465931478580854784" => "-6336351930",
        "37778931862957161709568" => "-4000846218",
                           };

    if ($x eq '1') {

        if (defined(my $value = $mertens_table->{$y})) {
            return _set_int($value);
        }

        if (HAS_PRIME_UTIL and $y < ULONG_MAX) {
            return _set_int($mertens_table->{$y} = Math::Prime::Util::mertens($y));
        }
    }

    # Support for large integers (slow for wide ranges)
    if ($y >= ~0) {

        $x = Math::GMPz::Rmpz_init_set_str("$x", 10);
        $y = Math::GMPz::Rmpz_init_set_str("$y", 10);

        my $sum = 0;

        for (; Math::GMPz::Rmpz_cmp($x, $y) <= 0 ; Math::GMPz::Rmpz_add_ui($x, $x, 1)) {
            $sum += Math::Prime::Util::GMP::moebius(Math::GMPz::Rmpz_get_str($x, 10));
        }

        return _set_int($sum);
    }

    return ZERO if ($y < $x);

    # Optimization for narrow ranges
    if (($x >= 10**4 and $y - $x <= 10**4) or "$x" / "$y" >= 0.999) {
        my $r = List::Util::sum(
                                HAS_PRIME_UTIL
                                ? Math::Prime::Util::moebius($x, $y)
                                : Math::Prime::Util::GMP::moebius($x, $y)
                               );
        return _set_int($r);
    }

    my $lookup_size = 2 * Math::Prime::Util::GMP::rootint($y, 3)**2;

    if ($y > 1e10) {
        $lookup_size >>= 1;
    }

    if ($y > 1e11) {
        $lookup_size >>= 1;
    }

    state @mertens_lookup;

    if (@mertens_lookup < $lookup_size) {
        $mertens_lookup[0] = 0;

        my @mu_range =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::moebius(scalar(@mertens_lookup), $lookup_size)
          : Math::Prime::Util::GMP::moebius(scalar(@mertens_lookup), $lookup_size);

        foreach my $i (@mertens_lookup .. $lookup_size) {
            $mertens_lookup[$i] = $mertens_lookup[$i - 1] + shift(@mu_range);
        }
    }

    use integer;

    my $mertens = sub {
        my ($n) = @_;

        # Algorithm based on the recursive identity:
        #   M(n) = 1 - Sum_{k=2..n} M(floor(n/k))

        if ($n <= $lookup_size) {
            return $mertens_lookup[$n];
        }

        if (exists $mertens_table->{$n}) {
            return $mertens_table->{$n};
        }

        # Using Dana Jacobsen's (++) optimizations from Math::Prime::Util::PP.
        my $s  = Math::Prime::Util::GMP::sqrtint($n);
        my $ns = $n / ($s + 1);

        my ($nk, $nk1) = ($n, $n >> 1);
        my $M = 1 - ($nk - $nk1);

        foreach my $k (2 .. $ns) {
            ($nk, $nk1) = ($nk1, $n / ($k + 1));
            $M -= ($nk <= $lookup_size) ? $mertens_lookup[$nk] : __SUB__->($nk);
            $M -= $mertens_lookup[$k] * ($nk - $nk1);
        }

        if ($s > $ns) {
            $M -= $mertens_lookup[$s] * ($n / $s - $ns);
        }

        $mertens_table->{$n} = $M;
    };

    my $value =
      ($x == 1)
      ? $mertens->($y)
      : ($mertens->($y) - $mertens->($x) + Math::Prime::Util::GMP::moebius($x));

    _set_int($value);
}

*moebius_sum = \&mertens;

sub exp_bigomega {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_positive || return ZERO;
    $k->pow($n->bigomega);
}

sub exp_bigomega_sum {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n);

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $n_obj = bless \$n;

    my @terms;
    foreach my $i (0 .. __ilog__($n, 2)) {
        my $i_obj = _set_int($i);
        push @terms, $i_obj->almost_prime_count($n_obj)->mul($k->pow($i_obj));
    }

    Sidef::Types::Number::Number::sum(@terms);
}

sub exp_omega {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_positive || return ZERO;
    $k->pow($n->omega);
}

sub exp_omega_sum {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n);

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $n_obj = bless \$n;

    if (!HAS_PRIME_UTIL and $k->eq(TWO)) {
        return $n_obj->usigma_sum(ZERO);
    }

    my @terms;
    foreach my $i (0 .. CORE::int($n_obj->lgrt->numify) + 1) {
        my $i_obj = _set_int($i);
        push @terms, $i_obj->omega_prime_count($n_obj)->mul($k->pow($i_obj));
    }

    Sidef::Types::Number::Number::sum(@terms);
}

sub exp_squarefree_omega {    # k^omega(n) * mu(n)^2
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_positive   || return ZERO;
    $n->is_squarefree || return ZERO;
    $k->pow($n->omega);
}

sub exp_squarefree_omega_sum {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n);

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $n_obj = bless \$n;

    my @terms;
    foreach my $i (0 .. CORE::int($n_obj->lgrt->numify) + 1) {
        my $i_obj = _set_int($i);
        push @terms, $i_obj->squarefree_almost_prime_count($n_obj)->mul($k->pow($i_obj));
    }

    Sidef::Types::Number::Number::sum(@terms);
}

sub liouville_sum {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->liouville_sum->sub($from->dec->liouville_sum);
    }

    my $n = $$from;

    if (ref($n)) {
        $n = _big2istr($n) // goto &nan;
    }

    $n > 0 or return ZERO;

    state $liouville_table = {

        # L(10^n), where L(x) is Liouville's summatory function.
        # OIES: https://oeis.org/A090410
        '1'                      => '1',
        '10'                     => '0',
        '100'                    => '-2',
        '1000'                   => '-14',
        '10000'                  => '-94',
        '100000'                 => '-288',
        '1000000'                => '-530',
        '10000000'               => '-842',
        '100000000'              => '-3884',
        '1000000000'             => '-25216',
        '10000000000'            => '-116026',
        '100000000000'           => '-342224',
        '1000000000000'          => '-522626',
        '10000000000000'         => '-966578',
        '100000000000000'        => '-7424752',
        '1000000000000000'       => '-29445104',
        '10000000000000000'      => '-97617938',
        '100000000000000000'     => '-271676470',
        '1000000000000000000'    => '-618117940',
        '10000000000000000000'   => '-810056106',
        '100000000000000000000'  => '-6260758462',
        '1000000000000000000000' => '-34541748676',
                             };

    if (defined(my $value = $liouville_table->{$n})) {
        return bless \$value;
    }

    if (HAS_PRIME_UTIL and $n < ULONG_MAX and $n > 1e6) {    # this is faster for large n
        return ((bless \$n)->exp_bigomega_sum(MONE));
    }

    my $L    = 0;
    my $sqrt = Math::Prime::Util::GMP::sqrtint($n);

    state $mertens_table = [ 0,  1,  0, -1, -1, -2, -1, -2, -2, -2, -1, -2, -2, -3, -2, -1, -1, -2, -2, -3, -3, -2, -1, -2, -2, -2,
                            -1, -1, -1, -2, -3, -4, -4, -3, -2, -1, -1, -2, -1,  0,  0, -1, -2, -3, -3, -3, -2, -3, -3, -3, -3, -2,
                            -2, -3, -3, -2, -2, -1,  0, -1, -1, -2, -1, -1, -1,  0, -1, -2, -2, -1, -2, -3, -3, -4, -3, -3, -3, -2,
                            -3, -4, -4, -4, -3, -4, -4, -3, -2, -1, -1, -2, -2, -1, -1,  0,  1,  2,  2,  1,  1,  1,  1,  0, -1, -2,
                           ];

    my $u = 0;
    my $k = 1;

    while ($k <= $sqrt) {

        my $t =
          (HAS_PRIME_UTIL and $n < ULONG_MAX)
          ? Math::Prime::Util::divint($n, $k * $k)
          : Math::Prime::Util::GMP::divint($n, (($k * $k < ULONG_MAX) ? ($k * $k) : Math::Prime::Util::GMP::mulint($k, $k)));

        my $v =
          (HAS_PRIME_UTIL and $n < ULONG_MAX)
          ? Math::Prime::Util::sqrtint(Math::Prime::Util::divint($n, $t))
          : Math::Prime::Util::GMP::sqrtint(Math::Prime::Util::GMP::divint($n, $t));

        my $f = $v - $u;
        my $M =
          ($t <= 103)
          ? $mertens_table->[$t]
          : ${((FAST_MODE and $t < ULONG_MAX) ? (bless \$t) : _set_int($t))->mertens};

        $L += $f * (ref($M) ? Math::GMPz::Rmpz_get_si($M) : $M);
        $k += $f;
        $u = $v;
    }

    $liouville_table->{$n} = $L;
    bless \$L;
}

sub cyclotomic_polynomial {
    my ($n, $x) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _any2ui($$n) // goto &nan;

    if (!defined($x)) {

        if ($n == 0) {
            return Sidef::Types::Number::Polynomial->new();
        }
        elsif ($n == 1) {
            return Sidef::Types::Number::Polynomial->new(ONE)->dec;
        }

        if ($n >= 100 and $USE_PARI_GP) {
            if (my $res = _execute_pari_gp("polcyclo($n)")) {
                return Sidef::Types::Number::Polynomial->new(Sidef::Types::String::String->new($res));
            }
        }

        my @factor_exp = _factor_exp($n);

        # Generate the squarefree divisors of n, along
        # with the number of prime factors of each divisor
        my @sd;
        foreach my $pe (@factor_exp) {
            push @sd, map { [$_->[0] * $pe->[0], $_->[1] + 1] } @sd;
            push @sd, [$pe->[0], 1];
        }

        push @sd, [1, 0];

        my (@num, @den);
        foreach my $pair (@sd) {
            my ($d, $c) = @$pair;

            my $t    = CORE::int($n / $d);
            my $base = Sidef::Types::Number::Polynomial->new(_set_int($t))->dec;

            if ($c % 2 == 0) {
                push @num, $base;
            }
            else {
                push @den, $base;
            }
        }

        return _binsplit(\@num, \&Sidef::Types::Number::Polynomial::mul)->div(_binsplit(\@den, \&Sidef::Types::Number::Polynomial::mul));
    }

    ref($x) eq __PACKAGE__ or _valid(\$x);
    $x = $$x;

    return ZERO if ($n == 0);

    return bless(\__dec__($x)) if ($n == 1);
    return bless(\__inc__($x)) if ($n == 2);

    my $x_is_mpz = ref($x) eq 'Math::GMPz';

    # Special case for x = 1: cyclotomic(n, 1) is A020500.
    if ($x_is_mpz ? (Math::GMPz::Rmpz_cmp_ui($x, 1) == 0) : __eq__($x, 1)) {
        my $k = Math::Prime::Util::GMP::is_prime_power($n) || return ONE;
        my $p = Math::Prime::Util::GMP::rootint($n, $k);
        return _set_int($p);
    }

    # Special case for x = -1: cyclotomic(n, -1) is A020513.
    if ($x_is_mpz ? (Math::GMPz::Rmpz_cmp_si($x, -1) == 0) : __eq__($x, -1)) {
        ($n % 2 == 0) || return ONE;
        my $k = Math::Prime::Util::GMP::is_prime_power($n >> 1) || return ONE;
        my $p = Math::Prime::Util::GMP::rootint($n >> 1, $k);
        return _set_int($p);
    }

    my @factor_exp = _factor_exp($n);

    # Generate the squarefree divisors of n, along
    # with the number of prime factors of each divisor
    my @sd;
    foreach my $pe (@factor_exp) {
        push @sd, map { [$_->[0] * $pe->[0], $_->[1] + 1] } @sd;
        push @sd, [$pe->[0], 1];
    }

    push @sd, [1, 0];

    my (@num, @den);
    foreach my $pair (@sd) {
        my ($d, $c) = @$pair;

        my $t    = CORE::int($n / $d);
        my $base = $x_is_mpz
          ? do {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_pow_ui($z, $x, $t);
            Math::GMPz::Rmpz_sub_ui($z, $z, 1);
            $z;
          }
          : __dec__(__pow__($x, $t));

        if ($c % 2 == 0) {
            push @num, $base;
        }
        else {
            push @den, $base;
        }
    }

    (scalar(@num) > 0 and scalar(@den) > 0) || return ONE;
    bless \__div__(_binsplit(\@num, \&__mul__), _binsplit(\@den, \&__mul__));
}

*cyclotomic = \&cyclotomic_polynomial;

sub cyclotomicmod {
    my ($n, $x, $m) = @_;

    _valid(\$n, \$x, \$m);

    my $M = $m;

    $n = _any2mpz($$n) // goto &nan;
    $x = $$x;
    $m = _any2mpz($$m) // goto &nan;

    Math::GMPz::Rmpz_sgn($m) || goto &nan;

    if (ref($x) ne 'Math::GMPz') {
        if (__is_int__($x)) {
            $x = _any2mpz($x) // goto &nan;
        }
        else {
            $x = _modular_rational($x, $m) // goto &nan;
        }
    }

    # n must be >= 0
    (Math::GMPz::Rmpz_sgn($n) || return ZERO) > 0
      or goto &nan;

    return ZERO if (Math::GMPz::Rmpz_cmp_ui($m, 1) == 0);

    return bless(\__dec__($x))->mod($M) if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0);
    return bless(\__inc__($x))->mod($M) if (Math::GMPz::Rmpz_cmp_ui($n, 2) == 0);

    # Special case for x = 1: cyclotomic(n, 1) is A020500.
    if (Math::GMPz::Rmpz_cmp_ui($x, 1) == 0) {
        my $k = Math::Prime::Util::GMP::is_prime_power($n) || return ONE;
        my $p = Math::Prime::Util::GMP::rootint($n, $k);
        return _set_int(Math::Prime::Util::GMP::modint($p, $m));
    }

    # Special case for x = -1: cyclotomic(n, -1) is A020513.
    if (Math::GMPz::Rmpz_cmp_si($x, -1) == 0) {
        Math::GMPz::Rmpz_even_p($n) || return ONE;
        my $o = $n >> 1;
        my $k = Math::Prime::Util::GMP::is_prime_power($o) || return ONE;
        my $p = Math::Prime::Util::GMP::rootint($o, $k);
        return _set_int(Math::Prime::Util::GMP::modint($p, $m));
    }

    my @factor_exp = _factor_exp($n);

    # Generate the squarefree divisors of n, along
    # with the number of prime factors of each divisor
    my @sd;
    foreach my $pe (@factor_exp) {
        my ($p) = @$pe;

        $p =
          (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_init_set_ui($p)
          : Math::GMPz::Rmpz_init_set_str("$p", 10);

        push @sd, map { [$_->[0] * $p, $_->[1] + 1] } @sd;
        push @sd, [$p, 1];
    }

    push @sd, [$ONE, 0];

    my $base = Math::GMPz::Rmpz_init();
    my $prod = Math::GMPz::Rmpz_init_set_ui(1);

    foreach my $pair (@sd) {
        my ($d, $c) = @$pair;

        Math::GMPz::Rmpz_divexact($base, $n, $d);
        Math::GMPz::Rmpz_powm($base, $x, $base, $m);    # x^(n/d) mod m
        Math::GMPz::Rmpz_sub_ui($base, $base, 1);

        if ($c % 2 == 1) {
            Math::GMPz::Rmpz_invert($base, $base, $m) || goto &nan;
        }

        Math::GMPz::Rmpz_mul($prod, $prod, $base);
        Math::GMPz::Rmpz_mod($prod, $prod, $m);
    }

    bless \$prod;
}

sub cyclotomic_factor {
    my ($n, @bases) = @_;

    # ── Input validation and normalisation ─────────────────────────────────────
    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0 or return _array();

    if (@bases) {
        _valid(\(@bases));
        @bases = grep { defined } map { _any2mpz($$_) } @bases;
    }
    else {
        @bases = map { _any2mpz($_) } (2 .. __ilog__($n, 2));
    }

    $n = Math::GMPz::Rmpz_init_set($n);    # take a mutable copy

    # ── Squarefree-divisor cache ────────────────────────────────────────────────
    #
    # The Möbius product formula for the cyclotomic polynomial is:
    #
    #   Φ_k(x) = ∏_{d | k}  (x^(k/d) − 1)^{μ(d)}
    # ───────────────────────────────────────────────────────────────────────────
    my %sd_cache;
    my $squarefree_divisors_of = sub {
        my ($k) = @_;
        return $sd_cache{$k} if exists $sd_cache{$k};

        my @sd;
        for my $pp (_factor_exp($k)) {
            my $p = $pp->[0];
            push @sd, map { [$_->[0] * $p, $_->[1] + 1] } @sd;
            push @sd, [$p, 1];
        }
        push @sd, [1, 0];    # trivial divisor d=1, ω(1)=0
        $sd_cache{$k} = \@sd;
    };

    my $t    = Math::GMPz::Rmpz_init();    # holds x^(k/d) − 1 (mod m)
    my $tsav = Math::GMPz::Rmpz_init();    # saves $t before a potentially failing invert
    my $prod = Math::GMPz::Rmpz_init();    # Möbius product accumulator
    my $g    = Math::GMPz::Rmpz_init();    # GCD / factor scratch

    # ── Compute Φ_k(x) mod m
    my $cyclotomicmod = sub {
        my ($k, $x, $m) = @_;

        Math::GMPz::Rmpz_set_ui($prod, 1);

        for my $pair (@{$squarefree_divisors_of->($k)}) {
            my ($d, $omega) = @$pair;

            # t ← x^(k/d) − 1  (mod m)
            Math::GMPz::Rmpz_powm_ui($t, $x, CORE::int($k / $d), $m);
            Math::GMPz::Rmpz_sub_ui($t, $t, 1);

            if ($omega & 1) {    # μ(d) = −1 → need modular inverse
                Math::GMPz::Rmpz_set($tsav, $t);    # preserve $t before invert overwrites it
                unless (Math::GMPz::Rmpz_invert($t, $t, $m)) {

                    # $t is not invertible mod $m.
                    # gcd($tsav, $m) > 1 is a non-trivial factor; store in $g for caller.
                    Math::GMPz::Rmpz_gcd($g, $tsav, $m);
                    return undef;
                }
            }

            Math::GMPz::Rmpz_mul($prod, $prod, $t);
            Math::GMPz::Rmpz_mod($prod, $prod, $m);
        }

        $prod;
    };

    my @factors;

  OUTER: for my $x (@bases) {
        my $limit = 1 + __ilog__($n, $x);

        for (my $k = 3 ; $k <= $limit ; ++$k) {
            my $phi = $cyclotomicmod->($k, $x, $n);

            # If cyclotomicmod returned undef, a non-trivial gcd was already
            # written into $g; otherwise compute gcd(Φ_k(x), n) now.
            Math::GMPz::Rmpz_gcd($g, $n, $phi) if defined $phi;

            # Skip trivial (= 1) or full (= n) GCDs.
            next if Math::GMPz::Rmpz_cmp_ui($g, 1) == 0;
            next if Math::GMPz::Rmpz_cmp($g, $n) == 0;

            # Extract all copies of the factor from n.
            my $f = Math::GMPz::Rmpz_init_set($g);         # own copy before $g is reused
            my $v = Math::GMPz::Rmpz_remove($n, $n, $f);
            push @factors, ($f) x $v;

            last OUTER if (   Math::GMPz::Rmpz_cmp_ui($n, 1) == 0
                           or _is_prob_prime($n));
        }
    }

    # If n was not fully factored, the remainder is a (probable) prime factor.
    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0 and push @factors, $n;

    @factors = sort { Math::GMPz::Rmpz_cmp($a, $b) } @factors;
    _array([map { bless \$_ } @factors]);
}

sub powerfree_sum {
    my ($k, $from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->powerfree_sum($to)->sub($k->powerfree_sum($from->dec));
    }

    ref($from) eq __PACKAGE__ or _valid(\$from);

    my $n = _any2mpz($$from) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    return ZERO if ($k == 0);
    return ONE  if ($k == 1);

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $w = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_root($t, $n, $k);
    Math::GMPz::Rmpz_fits_ulong_p($t) || goto &nan;    # too large

    my $s   = Math::GMPz::Rmpz_get_ui($t);
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    if (HAS_PRIME_UTIL) {
        Math::Prime::Util::forsquarefree(
            sub {

                # u = faulhaber(floor(n/v^k), 1)
                Math::GMPz::Rmpz_ui_pow_ui($w, $_, $k);
                Math::GMPz::Rmpz_div($t, $n, $w);
                Math::GMPz::Rmpz_mul($u, $t, $t);
                Math::GMPz::Rmpz_add($u, $u, $t);
                Math::GMPz::Rmpz_div_2exp($u, $u, 1);

                # u *= v^k
                Math::GMPz::Rmpz_mul($u, $u, $w);

                (scalar(@_) & 1)
                  ? Math::GMPz::Rmpz_sub($sum, $sum, $u)
                  : Math::GMPz::Rmpz_add($sum, $sum, $u);
            },
            $s
        );
    }
    else {
        my $m;
        for (my $v = 1 ; $v <= $s ; ++$v) {
            if ($m = HAS_PRIME_UTIL ? Math::Prime::Util::moebius($v) : Math::Prime::Util::GMP::moebius($v)) {

                # u = faulhaber(floor(n/v^k), 1)
                Math::GMPz::Rmpz_ui_pow_ui($w, $v, $k);
                Math::GMPz::Rmpz_div($t, $n, $w);
                Math::GMPz::Rmpz_mul($u, $t, $t);
                Math::GMPz::Rmpz_add($u, $u, $t);
                Math::GMPz::Rmpz_div_2exp($u, $u, 1);

                # u *= v^k
                Math::GMPz::Rmpz_mul($u, $u, $w);

                ($m == 1)
                  ? Math::GMPz::Rmpz_add($sum, $sum, $u)
                  : Math::GMPz::Rmpz_sub($sum, $sum, $u);
            }
        }
    }

    return bless \$sum;
}

sub squarefree_sum {
    (TWO)->powerfree_sum(@_);
}

sub cubefree_sum {
    (THREE)->powerfree_sum(@_);
}

sub _native_squarefree_count {
    my ($n) = @_;

    if (HAS_PRIME_UTIL) {
        return Math::Prime::Util::powerfree_count($n, 2);
    }

    my $s = Math::Prime::Util::GMP::sqrtint($n);

    if ($n < ((~0) >> 1)) {

        # Basic method from https://arxiv.org/pdf/1107.4890.pdf
        # Based on code by Dana Jacobsen (translated from Math::Prime::Util).

        use integer;

        my $I = Math::Prime::Util::GMP::rootint($n, 5);
        my $D = Math::Prime::Util::GMP::sqrtint($n / $I);

        my $S1 = $n;

        my $k       = 2;
        my @M       = (0, 1);
        my $mertens = 1;

        foreach my $mu (Math::Prime::Util::GMP::moebius(2, $D)) {
            if ($mu) {
                $S1      += $mu * ($n / ($k * $k));
                $mertens += $mu;
            }
            push @M, $mertens;
            ++$k;
        }

        my @Mx;
        my $Mxisum = 0;

        for (my $i = $I - 1 ; $i > 0 ; --$i) {

            my $Mxi = 1;
            my $xi  = Math::Prime::Util::GMP::sqrtint($n / $i);
            my $L   = Math::Prime::Util::GMP::sqrtint($xi);

            foreach my $j (1 .. ($xi / ($L + 1))) {
                $Mxi -= $M[$j] * (($xi / $j) - ($xi / ($j + 1)));
            }
            foreach my $j (2 .. $L) {
                $Mxi -= (($xi / $j) <= $D) ? $M[$xi / $j] : $Mx[$j * $j * $i];
            }
            $Mx[$i] = $Mxi;
            $Mxisum += $Mxi;
        }

        my $S2 = $Mxisum - ($I - 1) * $M[$D];

        return $S1 + $S2;
    }

    # Using segmented moebius(a, b)
    my $count      = 0;
    my $chunk_size = 1_000_000;

    for (my $start = 1 ; $start <= $s ; $start += $chunk_size) {
        my $end = $start + $chunk_size - 1;
        $end = $s if $end > $s;

        my @mu_chunk = Math::Prime::Util::GMP::moebius($start, $end);

        for my $i (0 .. $#mu_chunk) {
            my $m = $mu_chunk[$i];
            if ($m) {
                my $k = $start + $i;
                $count += $m * CORE::int($n / ($k * $k));
            }
        }
    }

    return $count;
}

sub powerfree_count {
    my ($k, $from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->powerfree_count($to)->sub($k->powerfree_count($from->dec));
    }

    ref($from) eq __PACKAGE__ or _valid(\$from);

    my $n = _any2mpz($$from) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    return ZERO if ($k == 0);
    return ONE  if ($k == 1);

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _set_int(Math::Prime::Util::powerfree_count(Math::GMPz::Rmpz_get_ui($n), $k));
    }

    # Squarefree count for native integers
    if ($k == 2 and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _set_int(_native_squarefree_count(Math::GMPz::Rmpz_get_ui($n)));
    }

    _set_int(Math::Prime::Util::GMP::powerfree_count(Math::GMPz::Rmpz_get_str($n, 10), $k));
}

sub squarefree_count {
    (TWO)->powerfree_count(@_);
}

*square_free_count = \&squarefree_count;

sub cubefree_count {
    (THREE)->powerfree_count(@_);
}

sub is_nonsquarefree {
    $_[0]->is_squarefree->not && $_[0]->is_pos;
}

sub is_noncubefree {
    $_[0]->is_cubefree->not && $_[0]->is_pos;
}

sub is_nonpowerfree {
    $_[0]->is_powerfree($_[1])->not && $_[0]->is_pos;
}

sub _sieve_nonpowerfree {
    my ($A, $B, $k) = @_;

    # $A and $B are Math::GMPz objects
    # $k is a non-negative native integer

    state $m = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my $t = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_root($t, $B, $k);

    my @arr;

    if (
        !Math::GMPz::Rmpz_fits_slong_p($t) or do {
            Math::GMPz::Rmpz_sub($m, $B, $A);
            Math::GMPz::Rmpz_cmp($m, $t) < 0;
        }
      ) {    # too large or small range

        Math::GMPz::Rmpz_set($t, $A);

        my $k_obj = bless \$k;
        my $t_obj = bless \$t;

        my $native_A = Math::GMPz::Rmpz_fits_ulong_p($A);

        for (; Math::GMPz::Rmpz_cmp($t, $B) <= 0 ; Math::GMPz::Rmpz_add_ui($t, $t, 1)) {
            if (HAS_PRIME_UTIL and $native_A and Math::GMPz::Rmpz_fits_ulong_p($t)) {
                unless (Math::Prime::Util::is_powerfree(Math::GMPz::Rmpz_get_ui($t), $k)) {
                    push @arr, Math::GMPz::Rmpz_get_ui($t);
                }
            }
            else {
                unless ($t_obj->is_powerfree($k_obj)) {
                    push @arr,
                      (
                          Math::GMPz::Rmpz_fits_ulong_p($t)
                        ? Math::GMPz::Rmpz_get_ui($t)
                        : Math::GMPz::Rmpz_init_set($t)
                      );
                }
            }
        }

        return \@arr;
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($B)) {

        $A = Math::GMPz::Rmpz_get_ui($A);
        $B = Math::GMPz::Rmpz_get_ui($B);

        foreach my $j (2 .. Math::GMPz::Rmpz_get_ui($t)) {

            my $t = 1;
            my $m = Math::Prime::Util::powint($j, $k);
            my $u = Math::Prime::Util::divint($B, $m);

            if ($A > $m) {
                $t = Math::Prime::Util::divint($A, $m);
                if (Math::Prime::Util::mulint($t, $m) < $A) {
                    ++$t;
                }
            }

            foreach my $s ($t .. $u) {
                if (Math::Prime::Util::is_powerfree($s, $k)) {
                    push @arr, Math::Prime::Util::mulint($m, $s);
                }
            }
        }
    }
    else {
        foreach my $j (2 .. Math::GMPz::Rmpz_get_ui($t)) {

            Math::GMPz::Rmpz_ui_pow_ui($m, $j, $k);
            Math::GMPz::Rmpz_cdiv_q($t, $A, $m);
            Math::GMPz::Rmpz_div($u, $B, $m);

            for (; Math::GMPz::Rmpz_cmp($t, $u) <= 0 ; Math::GMPz::Rmpz_add_ui($t, $t, 1)) {
                if (    HAS_PRIME_UTIL
                    and Math::GMPz::Rmpz_fits_ulong_p($t)
                    and Math::Prime::Util::is_powerfree(Math::GMPz::Rmpz_get_ui($t), $k)) {
                    my $z = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul_ui($z, $m, Math::GMPz::Rmpz_get_ui($t));
                    $z = Math::GMPz::Rmpz_get_ui($z) if Math::GMPz::Rmpz_fits_ulong_p($z);
                    push @arr, $z;
                }
                elsif ((bless \$t)->is_powerfree(bless \$k)) {
                    my $z = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($z, $m, $t);
                    $z = Math::GMPz::Rmpz_get_ui($z) if Math::GMPz::Rmpz_fits_ulong_p($z);
                    push @arr, $z;
                }
            }
        }
    }

    @arr = sort { $a <=> $b } @arr;
    return \@arr;
}

sub nonpowerfree {
    my ($k, $A, $B) = @_;

    ref($A) eq __PACKAGE__ or _valid(\$A);

    $k = _any2ui($$k) || return _array();
    $A = _any2mpz($$A) // return _array();

    if (defined($B)) {
        ref($B) eq __PACKAGE__ or _valid(\$B);
        $B = _any2mpz($$B) // return _array();
    }
    else {
        $B = $A;
        $A = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($A) <= 0) {
        $A = $ONE;
    }

    if (Math::GMPz::Rmpz_cmp($A, $B) > 0) {
        return _array();
    }

#<<<
    my @nonpowerfree = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_nonpowerfree($A, $B, $k) // return undef};
#>>>

    _array(\@nonpowerfree);
}

sub nonsquarefree {
    (TWO)->nonpowerfree(@_);
}

sub noncubefree {
    (THREE)->nonpowerfree(@_);
}

sub nonpowerfree_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    my $step_value = $TWO**$k;

    if ($step_value < 1e4) {
        $step_value = 1e4;
    }

    _generic_each($from, $to, $block, sub { $step_value }, sub { _sieve_nonpowerfree($_[0], $_[1], $k) });
}

*each_nonpowerfree = \&nonpowerfree_each;

sub nonsquarefree_each {
    (TWO)->nonpowerfree_each(@_);
}

*each_nonsquarefree = \&nonsquarefree_each;

sub noncubefree_each {
    (THREE)->nonpowerfree_each(@_);
}

*each_noncubefree = \&noncubefree_each;

sub nonpowerfree_count {
    my ($k, $from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__   or _valid(\$to);
        ref($from) eq __PACKAGE__ or _valid(\$from);

        return ZERO if $to->lt($from);

        return $to->sub($from)->inc->sub($k->powerfree_count($to)->sub($k->powerfree_count($from->dec)));
    }

    ref($from) eq __PACKAGE__ or _valid(\$from);
    $from->sub($k->powerfree_count($from));
}

sub nonsquarefree_count {
    (TWO)->nonpowerfree_count(@_);
}

sub noncubefree_count {
    (THREE)->nonpowerfree_count(@_);
}

sub nonpowerfree_sum {
    my ($k, $from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->nonpowerfree_sum($to)->sub($k->nonpowerfree_sum($from->dec));
    }

    ref($from) eq __PACKAGE__ or _valid(\$from);
    $from->faulhaber_sum(ONE)->sub($k->powerfree_sum($from));
}

sub nonsquarefree_sum {
    (TWO)->nonpowerfree_sum(@_);
}

sub noncubefree_sum {
    (THREE)->nonpowerfree_sum(@_);
}

sub nth_nonpowerfree {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 1 or goto &nan;
    }
    else {
        $k = 2;
    }

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        goto &nan;
    }

    if ($k == 1) {
        return ((bless \$n)->inc);
    }

    # If x is the n-th non-k-powerfree number, then:
    #   x/(x-n) ~ zeta(k)
    #   (x*zeta(k) - n*zeta(k))/x ~ 1
    #   zeta(k) - n*zeta(k)/x ~ 1

    my $prec = $k + Math::GMPz::Rmpz_sizeinbase($n, 2) + 2;

    my $f = Math::MPFR::Rmpfr_init2($prec);
    my $t = Math::MPFR::Rmpfr_init2($prec);
    my $u = Math::MPFR::Rmpfr_init2($prec);

    Math::MPFR::Rmpfr_zeta_ui($t, $k, $ROUND);      # t = zeta(k)
    Math::MPFR::Rmpfr_mul_z($f, $t, $n, $ROUND);    # f = n*zeta(k)

#<<<
    # Conjectured bounds:
    #   lower = ((zeta(k)*(n-1) / (zeta(k)-1)) - n)
    #   upper = ((zeta(k)*(n+1) / (zeta(k)-1)) + n)

    #~ my $w = Math::GMPz::Rmpz_init();
    #~ Math::GMPz::Rmpz_sub_ui($w, $n, 1);
    #~ Math::MPFR::Rmpfr_mul_z($f, $t, $w, $ROUND);
    #~ Math::MPFR::Rmpfr_sub_ui($u, $t, 1, $ROUND);
    #~ Math::MPFR::Rmpfr_div($f, $f, $u, $ROUND);
    #~ Math::MPFR::Rmpfr_sub_z($f, $f, $n, $ROUND);

    #~ my $min = bless \(_any2mpz($f) // goto &nan);

    #~ Math::GMPz::Rmpz_add_ui($w, $n, 1);
    #~ Math::MPFR::Rmpfr_mul_z($f, $t, $w, $ROUND);
    #~ Math::MPFR::Rmpfr_div($f, $f, $u, $ROUND);
    #~ Math::MPFR::Rmpfr_add_z($f, $f, $n, $ROUND);

    #~ my $max = bless \(_any2mpz($f) // goto &nan);
#>>>

    my $w = Math::GMPz::Rmpz_init_set($n);
    my $v = Math::GMPz::Rmpz_init();

    while (1) {

        Math::GMPz::Rmpz_mul_2exp($w, $w, 1);
        Math::MPFR::Rmpfr_div_z($u, $f, $w, $ROUND);    # u = n*zeta(k)/w
        Math::MPFR::Rmpfr_sub($u, $t, $u, $ROUND);      # u = zeta(k) - n*zeta(k)/w

        if (Math::MPFR::Rmpfr_cmp_ui($u, 1) >= 0) {

            if (Math::MPFR::Rmpfr_cmp_ui($u, 1) == 0) {    # exact result
                return bless \$w;
            }

            last;
        }
    }

    Math::GMPz::Rmpz_div_2exp($v, $w, 1);

    my $min = bless \$v;
    my $max = bless \$w;

    my $n_obj = bless \$n;
    my $k_obj = bless \$k;

    bsearch_min(
        $min, $max,
        Sidef::Types::Block::Block->new(
            code => sub {
                $k_obj->nonpowerfree_count($_[0])->cmp($n_obj);
            }
        )
    );
}

sub nth_nonsquarefree {
    my ($n) = @_;
    $n->nth_nonpowerfree(TWO);
}

sub nth_noncubefree {
    my ($n) = @_;
    $n->nth_nonpowerfree(THREE);
}

sub next_nonpowerfree {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
    }
    else {
        $k = TWO;
    }

    # TODO: optimize when n^(1/k) is too large.

    $k->nonpowerfree_count($n)->inc->nth_nonpowerfree($k);
}

sub next_nonsquarefree {
    my ($n) = @_;
    $n->next_nonpowerfree(TWO);
}

sub next_noncubefree {
    my ($n) = @_;
    $n->next_nonpowerfree(THREE);
}

sub prev_nonpowerfree {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
    }
    else {
        $k = TWO;
    }

    # TODO: optimize when n^(1/k) is too large.

    my $count = $k->nonpowerfree_count($n);
    $count = $count->dec if $n->is_nonpowerfree($k);
    $count->nth_nonpowerfree($k);
}

sub prev_nonsquarefree {
    my ($n) = @_;
    $n->prev_nonpowerfree(TWO);
}

sub prev_noncubefree {
    my ($n) = @_;
    $n->prev_nonpowerfree(THREE);
}

sub _sieve_powerfree {
    my ($A, $B, $k) = @_;

    # $A and $B are Math::GMPz objects
    # $k is a non-negative native integer

    my $nonpowerfree = _sieve_nonpowerfree($A, $B, $k) // return undef;

    my $i     = 0;
    my $max_i = $#{$nonpowerfree};

    my @powerfree;
    if (Math::GMPz::Rmpz_fits_slong_p($B)) {

        $A = Math::GMPz::Rmpz_get_ui($A);
        $B = Math::GMPz::Rmpz_get_ui($B);

        foreach my $k ($A .. $B) {
            if ($i <= $max_i and $k == $nonpowerfree->[$i]) {
                ++$i;
            }
            else {
                push @powerfree, $k;
            }
        }
    }
    else {
        my $t = Math::GMPz::Rmpz_init_set($A);

        for (; Math::GMPz::Rmpz_cmp($t, $B) <= 0 ; Math::GMPz::Rmpz_add_ui($t, $t, 1)) {
            if ($i <= $max_i and $t == $nonpowerfree->[$i]) {
                ++$i;
            }
            else {
                push @powerfree,
                  (
                      Math::GMPz::Rmpz_fits_ulong_p($t)
                    ? Math::GMPz::Rmpz_get_ui($t)
                    : Math::GMPz::Rmpz_init_set($t)
                  );
            }
        }
    }

    return \@powerfree;
}

sub powerfree {
    my ($k, $A, $B) = @_;

    ref($A) eq __PACKAGE__ or _valid(\$A);

    $k = _any2ui($$k) || return _array();
    $A = _any2mpz($$A) // return _array();

    if (defined($B)) {
        ref($B) eq __PACKAGE__ or _valid(\$B);
        $B = _any2mpz($$B) // return _array();
    }
    else {
        $B = $A;
        $A = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($A) <= 0) {
        $A = $ONE;
    }

    if (Math::GMPz::Rmpz_cmp($A, $B) > 0) {
        return _array();
    }

    if ($k == 2) {
        return ((bless \$A)->squarefree(bless \$B));
    }

#<<<
    my @powerfree = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_powerfree($A, $B, $k) // return undef};
#>>>

    _array(\@powerfree);
}

sub cubefree {
    (THREE)->powerfree(@_);
}

sub powerfree_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 2) {
        return ((bless \$from)->squarefree_each((bless \$to), $block));
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    my $step_value = 1e3;
    _generic_each($from, $to, $block, sub { $step_value }, sub { _sieve_powerfree($_[0], $_[1], $k) });
}

*each_powerfree = \&powerfree_each;

sub cubefree_each {
    (THREE)->powerfree_each(@_);
}

*each_cubefree = \&cubefree_each;

sub _nth_prime_lower {
    my ($n) = @_;
    CORE::int($n * (CORE::log($n) + CORE::log(CORE::log($n)) - 1));
}

sub _nth_prime_upper {
    my ($n) = @_;
    CORE::int($n * (CORE::log($n) + CORE::log(CORE::log($n))));
}

sub _nth_almost_prime_lower {
    my ($n, $k) = @_;

    # Edge Case: Basic validity
    return 1 if $n <= 0;
    return 2 if $n == 1;

    # For small n, the asymptotic formula may underestimate
    return 2 if $n < $k;

    # Case k=1: The Primes
    # The bound n * ln(n) is a valid lower bound for the nth prime for n >= 1.
    # (Rosser's Theorem states p_n > n * ln(n))
    if ($k == 1) {
        return CORE::int($n * CORE::log($n));
    }

    # We need ln(ln(n)) > 1 to ensure the divisor doesn't artificially inflate
    # the result (which would violate the "lower bound" property).
    # ln(ln(n)) > 1 implies ln(n) > e, or n > e^e (~15.15).
    # For n < 16, this asymptotic formula is unstable/inaccurate.
    return 0 if $n < 16;

    # Calculate (k-1)!
    my $fact_km1 = 1;
    $fact_km1 *= $_ for 2 .. ($k - 1);

    my $log_n     = CORE::log($n);
    my $log_log_n = CORE::log($log_n);

    # The asymptotic form is: n * ln(n) * (k-1)! / (ln(ln(n)))^(k-1)
    # Formula based on the inverse of Landau's approximation for pi_k(x)
    my $numerator   = $n * $log_n * $fact_km1;
    my $denominator = $log_log_n**($k - 1);

    # Result is truncated to an integer
    return CORE::int($numerator / $denominator);
}

# ---------------------------------------------------------------------------
# _prime_count_step($y_str)
# Compute an adaptive sieve-step for _prime_count_range.
# Returns a step clamped to [1, SIEVE_UTIL_MAX].
# ---------------------------------------------------------------------------
sub _prime_count_step {
    my ($y_str) = @_;

    # Use native float arithmetic intentionally here; precision loss is
    # acceptable because the result is only a performance hint and is
    # clamped below.
    my $y_approx = 0 + $y_str;    # explicit coercion
    if ($y_approx > 0) {
        my $step = _nth_prime_lower($y_approx + CORE::log($y_approx) * 2e3) - _nth_prime_lower($y_approx);
        return $step if ($step > 0 && $step <= SIEVE_UTIL_MAX && $step <= ULONG_MAX);
    }

    return SIEVE_INLINE_MAX;      # safe fallback
}

# ---------------------------------------------------------------------------
# _prime_count_range($x, $y)
# Count primes in the closed interval [$x, $y].
# Both arguments are decimal strings
# ---------------------------------------------------------------------------
sub _prime_count_range {
    my ($x, $y) = @_;

    my $diff = Math::Prime::Util::GMP::subint($y, $x);

    if ($diff < 0) {
        return 0;
    }

    if ($diff eq '0') {
        return (_is_prob_prime($x) ? 1 : 0);
    }

    # Fast path: native Math::Prime::Util handles small ranges up to ULONG_MAX
    if (HAS_PRIME_UTIL and $y < ULONG_MAX) {
        return Math::Prime::Util::prime_count("$x", "$y");
    }

    # Medium path: GMP sieve fits in memory
    if ($diff <= SIEVE_INLINE_MAX) {
        my $count = () = Math::Prime::Util::GMP::sieve_primes($x, $y);
        return $count;
    }

    # Large path: walk in adaptive steps so each sieve segment stays manageable
    my $step = _prime_count_step("$y");

    my $count = 0;
    my $i     = Math::GMPz::Rmpz_init_set_str("$x", 10);
    Math::GMPz::Rmpz_sub_ui($i, $i, 1);    # start one below $x

    my $end = Math::GMPz::Rmpz_init_set_str("$y", 10);

    while (Math::GMPz::Rmpz_cmp($i, $end) <= 0) {
        my $from = $i + 1;
        my $to   = $i + $step;
        $to = $end if $to > $end;

        $count += () = Math::Prime::Util::GMP::sieve_primes($from, $to);

        Math::GMPz::Rmpz_add_ui($i, $i, $step);
    }

    return $count;
}

# ---------------------------------------------------------------------------
# _count_primes_forward($checkpoint, $target, $initial_count)
# Return pi($target) given pi($checkpoint) = $initial_count,
# where $target >= $checkpoint.
# ---------------------------------------------------------------------------
sub _count_primes_forward {
    my ($checkpoint, $target, $initial_count) = @_;

    my $add   = 0;
    my $delta = $target - $checkpoint;

    if (FAST_MODE and $delta <= NAIVE_LOOP_MAX and $target < ULONG_MAX) {

        # Convert to native integers once, before the inner loop
        my $n  = ref($target)     ? Math::GMPz::Rmpz_get_ui($target)     : $target;
        my $cp = ref($checkpoint) ? Math::GMPz::Rmpz_get_ui($checkpoint) : $checkpoint;

        for (my $k = $cp + 1 ; $k <= $n ; ++$k) {
            ++$add
              if (
                  HAS_PRIME_UTIL
                  ? Math::Prime::Util::is_prime($k)
                  : Math::Prime::Util::GMP::is_prime($k)
                 );
        }
    }
    else {
        # Larger delta — delegate to segmented sieve
        $add = _prime_count_range(Math::Prime::Util::GMP::addint($checkpoint, 1), "$target");
    }

    return Math::Prime::Util::GMP::addint($initial_count, $add);
}

# ---------------------------------------------------------------------------
# _count_primes_backward($checkpoint, $target, $initial_count)
# Return pi($target) given pi($checkpoint) = $initial_count,
# where $target <= $checkpoint.
# ---------------------------------------------------------------------------
sub _count_primes_backward {
    my ($checkpoint, $target, $initial_count) = @_;

    my $subtract = 0;
    my $delta    = $checkpoint - $target;

    if (FAST_MODE and $delta <= NAIVE_LOOP_MAX and $checkpoint < ULONG_MAX) {

        # Convert to native integers once, before the inner loop
        my $n  = ref($target)     ? Math::GMPz::Rmpz_get_ui($target)     : $target;
        my $cp = ref($checkpoint) ? Math::GMPz::Rmpz_get_ui($checkpoint) : $checkpoint;

        for (my $k = $n + 1 ; $k <= $cp ; ++$k) {
            ++$subtract
              if (
                  HAS_PRIME_UTIL
                  ? Math::Prime::Util::is_prime($k)
                  : Math::Prime::Util::GMP::is_prime($k)
                 );
        }
    }
    else {
        # Larger delta — delegate to segmented sieve
        $subtract = _prime_count_range(Math::Prime::Util::GMP::addint($target, 1), "$checkpoint");
    }

    return Math::Prime::Util::GMP::subint($initial_count, $subtract);
}

# ---------------------------------------------------------------------------
# _find_prime_count_checkpoint($n, $sorted_keys_aref)
# Binary-search $sorted_keys_aref for the key closest to $n.
# Returns ($key, $distance, $direction) where $direction is
# 'above' (count up from key) or 'below' (count down from key),
# or (undef, undef, undef) when the table is empty.
# ---------------------------------------------------------------------------
sub _find_prime_count_checkpoint {
    my ($n, $keys) = @_;

    if ($n > ULONG_MAX) {
        $n = Math::GMPz::Rmpz_init_set_str("$n", 10);
    }

    my ($lo, $hi, $idx) = (0, $#$keys, -1);

    while ($lo <= $hi) {
        my $mid = ($lo + $hi) >> 1;
        my $cmp = $keys->[$mid] <=> $n;

        if ($cmp == 0) {
            $idx = $mid;
            last;    # exact hit
        }
        elsif ($cmp < 0) {
            $idx = $mid;       # best lower-bound candidate so far
            $lo  = $mid + 1;
        }
        else {
            $hi = $mid - 1;
        }
    }

    my $key_below = $idx >= 0      ? $keys->[$idx]     : undef;
    my $key_above = $idx < $#$keys ? $keys->[$idx + 1] : undef;

    # Pick whichever neighbour is closer; prefer counting upward on a tie
    if (defined $key_below && defined $key_above) {
        my $d_below = $n - $key_below;
        my $d_above = $key_above - $n;
        return (
                  ($d_below <= $d_above)
                ? ($key_below, $d_below, 'above')
                : ($key_above, $d_above, 'below')
               );
    }

    defined($key_below)
      ? ($key_below, $n - $key_below, 'above')
      : ($key_above, $key_above - $n, 'below');
}

# ---------------------------------------------------------------------------
# _count_from_checkpoint($y, $primepi_lookup, $primepi_keys)
# Internal helper: find the nearest precomputed checkpoint and walk to $y.
# Returns pi($y), or undef if no usable checkpoint exists.
# ---------------------------------------------------------------------------
sub _count_from_checkpoint {
    my ($y, $lookup, $keys) = @_;

    my ($checkpoint, $delta, $direction) = _find_prime_count_checkpoint($y, $keys);

    return undef unless defined $checkpoint;

    my $threshold = CORE::int(CORE::sqrt($checkpoint));
    $threshold = SIEVE_UTIL_MAX if ($threshold > SIEVE_UTIL_MAX);

    return undef if $delta > $threshold;

    say STDERR "prime_count($y): incremental from checkpoint $checkpoint (delta=$delta)"
      if $VERBOSE;

    my $count = $lookup->{"$checkpoint"};

    $direction eq 'above'
      ? _count_primes_forward($checkpoint, $y, $count)
      : _count_primes_backward($checkpoint, $y, $count);
}

# ---------------------------------------------------------------------------
# _build_pi_table($limit)
# Return a reference to an array A where A[n] = pi(n) for 0 <= n <= $limit.
# Built via a simple linear cumulative sum over a sieve bitfield — O(limit).
# ---------------------------------------------------------------------------
sub _build_pi_table {
    my ($limit) = @_;

    my @pi;

    my $k     = 0;
    my $count = 0;

    foreach my $p (@{_primes(2, $limit)}) {
        splice(@pi, $k, $p - $k + 1, ($count) x ($p - $k + 1));
        $k = $p;
        ++$count;
    }

    \@pi;
}

# ---------------------------------------------------------------------------
# _prime_count($x, $y)
# Internal: count primes in [$x, $y].  Both arguments are decimal strings.
# ---------------------------------------------------------------------------
sub _prime_count {
    my ($x, $y) = @_;

    # Normalise argument form: single arg means pi(y), two args means pi(x,y)
    if (defined $y) {
        $x = 2 if $x < 2;
    }
    else {
        ($x, $y) = (2, $x);
    }

    return 0 if ($y < $x or $y <= 1);

    # ----------------------------------------------------------------
    # State initialised once on first call
    # ----------------------------------------------------------------
    state $primepi_lookup = \%PRIMEPI_LOOKUP;

    state $primepi_lookup_keys = [sort { Math::GMPz::Rmpz_cmp($a, $b) }
                                  map  { (FAST_MODE and $_ < ULONG_MAX) ? Math::GMPz::Rmpz_init_set_ui($_) : Math::GMPz::Rmpz_init_set_str($_, 10) }
                                    keys %PRIMEPI_LOOKUP
                                 ];

    # Small dense pi(n) table for the range [0, $table_len]
    state $table_len = _next_prime(HAS_PRIME_UTIL ? 1e5 : 1e6);
    state $pi_table  = _build_pi_table($table_len);

    # ----------------------------------------------------------------
    # Fast path 1: $y fits in the dense table (O(1) lookup)
    # ----------------------------------------------------------------
    if ($x eq '2' && $y < $table_len) {
        return $pi_table->[$y];
    }

    # ----------------------------------------------------------------
    # Fast path 2: exact match in the precomputed lookup
    # ----------------------------------------------------------------
    if ($x eq '2') {
        if (defined(my $val = $primepi_lookup->{$y})) {
            return $val;
        }
    }

    # ----------------------------------------------------------------
    # Fast path 3: native Math::Prime::Util handles small $y efficiently
    # ----------------------------------------------------------------
    if (HAS_PRIME_UTIL && $y < 1e7) {
        return Math::Prime::Util::prime_count($x, $y);
    }

    my $diff = Math::Prime::Util::GMP::subint($y, $x);

    # ----------------------------------------------------------------
    # Medium path: checkpoint-based incremental counting
    # Worth trying when the range is large enough that a sieve over the
    # full interval would be slow, but the nearest precomputed value is
    # close enough to $y to make walking there cheap.
    # ----------------------------------------------------------------
    if ($diff > 1e6) {
        my $y_count = _count_from_checkpoint($y, $primepi_lookup, $primepi_lookup_keys);

        if (defined $y_count) {
            return $y_count if $x eq '2';
            my $x_count = _prime_count(Math::Prime::Util::GMP::subint($x, 1));
            return Math::Prime::Util::GMP::subint($y_count, $x_count);
        }
    }

    # ----------------------------------------------------------------
    # Large path 1: external primecount binary (arbitrary precision)
    # ----------------------------------------------------------------
    if ($diff > PRIMECOUNT_MIN && $USE_PRIMECOUNT) {
        my $y_count = `primecount $y`;

        if ($? == 0 && defined $y_count) {
            chomp $y_count;
            my $x_count = _prime_count(Math::Prime::Util::GMP::subint($x, 1));
            return Math::Prime::Util::GMP::subint($y_count, $x_count);
        }
    }

    # ----------------------------------------------------------------
    # Large path 2: narrow range near the top of the interval —
    # use a targeted sieve rather than a full pi(y) computation.
    # ----------------------------------------------------------------
    my $x_str    = "$x";
    my $y_str    = "$y";
    my $near_top = ($y >= 1e8 && $diff <= 1e4) || ($x_str / $y_str >= 0.9999);

    if ($near_top) {

        if (HAS_PRIME_UTIL && $y < PRIMECOUNT_MIN) {
            return Math::Prime::Util::prime_count($x_str, $y_str);
        }

        if ($diff >= 1e5 && $diff <= SIEVE_INLINE_MAX) {
            my $count = () = Math::Prime::Util::GMP::sieve_primes($x_str, $y_str);
            return $count;
        }

        return _prime_count_range($x_str, $y_str);
    }

    # ----------------------------------------------------------------
    # Large path 3: native Math::Prime::Util for sub-ULONG ranges
    # ----------------------------------------------------------------
    if (HAS_PRIME_UTIL and $y < ULONG_MAX) {
        return Math::Prime::Util::prime_count($x_str, $y_str);
    }

    my $y_count = _count_from_checkpoint($y, $primepi_lookup, $primepi_lookup_keys);

    if (defined $y_count) {
        return $y_count if $x_str eq '2';
        my $x_count = _prime_count(Math::Prime::Util::GMP::subint($x_str, 1));
        return Math::Prime::Util::GMP::subint($y_count, $x_count);
    }

    # ----------------------------------------------------------------
    # Fallback: full segmented sieve over the entire range
    # ----------------------------------------------------------------
    _prime_count_range($x_str, $y_str);
}

# ---------------------------------------------------------------------------
# prime_count (public method)
# ---------------------------------------------------------------------------
sub prime_count {
    my ($x, $y) = @_;

    if (defined $y) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $x = _big2istr($$x) // return ZERO;
        $x = 2 if $x < 2;
        $y = _big2uistr($$y) // return ZERO;
    }
    else {
        $y = _big2uistr($$x) // return ZERO;
        $x = 2;
    }

    return ZERO if $y < $x;

    _set_int(_prime_count($x, $y));
}

*primepi      = \&prime_count;
*count_primes = \&prime_count;
*primes_count = \&prime_count;

sub prime_count_lower {
    my ($n) = @_;

    $n = $$n;

    if (HAS_PRIME_UTIL and _fits_ulong($n)) {
        return _set_int(Math::Prime::Util::prime_count_lower(_get_ulong($n)));
    }

    $n = _any2mpz($n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    _set_int(Math::Prime::Util::GMP::prime_count_lower(Math::GMPz::Rmpz_get_str($n, 10)));
}

*primepi_lower = \&prime_count_lower;

sub prime_count_upper {
    my ($n) = @_;

    $n = $$n;

    if (HAS_PRIME_UTIL and _fits_ulong($n)) {
        return _set_int(Math::Prime::Util::prime_count_upper(_get_ulong($n)));
    }

    $n = _any2mpz($n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    _set_int(Math::Prime::Util::GMP::prime_count_upper(Math::GMPz::Rmpz_get_str($n, 10)));
}

*primepi_upper = \&prime_count_upper;

sub prime_power_count_lower {
    my ($n) = @_;

    $n = $$n;

    if (HAS_PRIME_UTIL and _fits_ulong($n)) {
        return _set_int(Math::Prime::Util::prime_power_count_lower(_get_ulong($n)));
    }

    $n = _any2mpz($n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $t     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $k (1 .. __ilog__($n, 2)) {
        Math::GMPz::Rmpz_root($t, $n, $k);
        my $v = ${(bless \$t)->prime_count_lower};
        ref($v)
          ? Math::GMPz::Rmpz_add($count, $count, $v)
          : Math::GMPz::Rmpz_add_ui($count, $count, $v);
    }

    bless \$count;
}

sub prime_power_count_upper {
    my ($n) = @_;

    $n = $$n;

    if (HAS_PRIME_UTIL and _fits_ulong($n)) {
        return _set_int(Math::Prime::Util::prime_power_count_upper(_get_ulong($n)));
    }

    $n = _any2mpz($n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $t     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $k (1 .. __ilog__($n, 2)) {
        Math::GMPz::Rmpz_root($t, $n, $k);
        my $v = ${(bless \$t)->prime_count_upper};
        ref($v)
          ? Math::GMPz::Rmpz_add($count, $count, $v)
          : Math::GMPz::Rmpz_add_ui($count, $count, $v);
    }

    bless \$count;
}

sub composite_count_lower {
    my ($n) = @_;
    $n->sub($n->prime_count_upper)->dec;
}

sub composite_count_upper {
    my ($n) = @_;
    $n->sub($n->prime_count_lower)->dec;
}

sub _nth_composite_lower_bound {
    my ($n) = @_;

    # n + n/log(n) + n/(log(n)**2)
    my $log_n = $n->log;
    $n->add($n->div($log_n))->add($n->div($log_n->mul($log_n)));
}

sub _nth_composite_upper_bound {
    my ($n) = @_;

    # n + n/log(n) + (3*n)/(log(n)**2))
    my $log_n = $n->log;
    $n->add($n->div($log_n))->add($n->mul(THREE)->div($log_n->mul($log_n)));
}

sub nth_composite_lower {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($z) <= 0) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp_ui($z, 3) <= 0) {
        return _set_int(4);
    }

    bsearch_min(
        $n->_nth_composite_lower_bound,
        $n->_nth_composite_upper_bound,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->composite_count_upper->cmp($n);
            }
        )
    );
}

*composite_lower = \&nth_composite_lower;

sub nth_composite_upper {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($z) < 0) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp_ui($z, 3) <= 0) {
        return _set_int(8);
    }

    bsearch_max(
        $n->_nth_composite_lower_bound,
        $n->_nth_composite_upper_bound,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->composite_count_lower->cmp($n);
            }
        )
    );
}

*composite_upper = \&nth_composite_upper;

sub _nth_prime_lower_bound {
    my ($n) = @_;
    my $log_n = $n->log;
    $n->mul($log_n->add($log_n->log)->dec);
}

sub _nth_prime_upper_bound {
    my ($n) = @_;
    my $log_n = $n->log;
    $n->mul($log_n->add($log_n->log));
}

sub nth_prime_lower {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_cmp_ui($z, 10) <= 0) {
        return $n->nth_prime;
    }

    bsearch_min(
        $n->_nth_prime_lower_bound,
        $n->_nth_prime_upper_bound,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->prime_count_upper->cmp($n);
            }
        )
    );
}

*prime_lower = \&nth_prime_lower;

sub nth_prime_upper {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_cmp_ui($z, 10) <= 0) {
        return $n->nth_prime;
    }

    bsearch_max(
        $n->_nth_prime_lower_bound,
        $n->_nth_prime_upper_bound,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->prime_count_lower->cmp($n);
            }
        )
    );
}

*prime_upper = \&nth_prime_upper;

sub nth_prime_power_lower {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($z) <= 0) {
        return ZERO;
    }

    bsearch_min(
        $n,
        $n->nth_prime_upper,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->prime_power_count_upper->cmp($n);
            }
        )
    );
}

*prime_power_lower = \&nth_prime_power_lower;

sub nth_prime_power_upper {
    my ($n) = @_;

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($z) <= 0) {
        return ZERO;
    }

    bsearch_max(
        $n,
        $n->nth_prime_upper,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->prime_power_count_lower->cmp($n);
            }
        )
    );
}

*prime_power_upper = \&nth_prime_power_upper;

sub almost_prime_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {

        # TODO: implement k-almost prime counting in a given range and use it when |from-to| < sqrt(to).
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->almost_prime_count($to)->sub($k->almost_prime_count($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_count;
    }
    elsif ($k == 2) {
        return $_[1]->semiprime_count;
    }

    my $n = _any2mpz($$from) // return ZERO;

    if (Math::GMPz::Rmpz_cmp_ui($n, $k) <= 0) {
        return ZERO;
    }

    if ($k == 0) {
        return ONE;
    }

    state $pi_k_lookup = {
        '3' => {
                "10000000"             => "2444359",
                "100000000"            => "23727305",
                "1000000000"           => "229924367",
                "10000000000"          => "2227121996",
                "100000000000"         => "21578747909",
                "1000000000000"        => "209214982913",
                "10000000000000"       => "2030133769624",
                "100000000000000"      => "19717814526785",
                "1000000000000000"     => "191693417109381",
                "10000000000000000"    => "1865380637252270",
                "100000000000000000"   => "18168907486812690",
                "1000000000000000000"  => "177123437184971927",
                "10000000000000000000" => "1728190923820610000",
               },

        '4' => {
                "10000000"             => "2050696",
                "100000000"            => "20959322",
                "1000000000"           => "212385942",
                "10000000000"          => "2139236881",
                "100000000000"         => "21454599814",
                "1000000000000"        => "214499908019",
                "10000000000000"       => "2139634739326",
                "100000000000000"      => "21306682904040",
                "1000000000000000"     => "211905511283590",
                "10000000000000000"    => "2105504493045818",
                "100000000000000000"   => "20905484578206982",
                "1000000000000000000"  => "207458897819329031",
                "10000000000000000000" => "2057931819347474462",
               },

        '5' => {
                "10000000"            => "1349779",
                "100000000"           => "14371023",
                "1000000000"          => "150982388",
                "10000000000"         => "1570678136",
                "100000000000"        => "16218372618",
                "1000000000000"       => "166497674684",
                "10000000000000"      => "1701439985694",
                "100000000000000"     => "17323079621014",
                "1000000000000000"    => "175846040834673",
                "10000000000000000"   => "1780617141307093",
                "100000000000000000"  => "17993699600756449",
                "1000000000000000000" => "181520864946969233",
               },

        '6' => {
                "10000000"            => "774078",
                "100000000"           => "8493366",
                "1000000000"          => "91683887",
                "10000000000"         => "977694273",
                "100000000000"        => "10327249593",
                "1000000000000"       => "108264085934",
                "10000000000000"      => "1128049914377",
                "100000000000000"     => "11694704489580",
                "1000000000000000"    => "120734708167792",
                "10000000000000000"   => "1242063105505230",
                "100000000000000000"  => "12739510126065301",
                "1000000000000000000" => "130330025583399801",
               },

        '7' => {
                "10000000"            => "409849",
                "100000000"           => "4600247",
                "1000000000"          => "50678212",
                "10000000000"         => "550454756",
                "100000000000"        => "5913771637",
                "1000000000000"       => "62981797962",
                "10000000000000"      => "665997804082",
                "100000000000000"     => "7001087934965",
                "1000000000000000"    => "73232029374751",
                "10000000000000000"   => "762783057783010",
                "100000000000000000"  => "7916319351632036",
                "1000000000000000000" => "81898808371556517",
               },

        '8' => {
                "10000000"            => "207207",
                "100000000"           => "2367507",
                "1000000000"          => "26483012",
                "10000000000"         => "291646797",
                "100000000000"        => "3173159326",
                "1000000000000"       => "34192782745",
                "10000000000000"      => "365561221293",
                "100000000000000"     => "3882841742380",
                "1000000000000000"    => "41015564702074",
                "10000000000000000"   => "431227959019552",
                "100000000000000000"  => "4515480975731045",
                "1000000000000000000" => "47115876816676830",
               },

        '9' => {
                "10000000"             => "101787",
                "100000000"            => "1180751",
                "1000000000"           => "13377156",
                "10000000000"          => "148930536",
                "100000000000"         => "1636170477",
                "1000000000000"        => "17787688377",
                "10000000000000"       => "191742524399",
                "100000000000000"      => "2052389350029",
                "1000000000000000"     => "21838745177567",
                "10000000000000000"    => "231206458686127",
                "100000000000000000"   => "2437121982958248",
                "1000000000000000000"  => "25591920108631224",
                "10000000000000000000" => "267840642082525459",
               },

        '10' => {
                 "10000000"             => "49163",
                 "100000000"            => "578154",
                 "1000000000"           => "6618221",
                 "10000000000"          => "74342563",
                 "100000000000"         => "823164388",
                 "1000000000000"        => "9011965866",
                 "10000000000000"       => "97765974368",
                 "100000000000000"      => "1052666075366",
                 "1000000000000000"     => "11263041623194",
                 "10000000000000000"    => "119864659464824",
                 "100000000000000000"   => "1269754732725522",
                 "1000000000000000000"  => "13396817167474205",
                 "10000000000000000000" => "140847445420555406",
                },

        '11' => {
                 "10000000"             => "23448",
                 "100000000"            => "279286",
                 "1000000000"           => "3230577",
                 "10000000000"          => "36585097",
                 "100000000000"         => "407818620",
                 "1000000000000"        => "4490844534",
                 "10000000000000"       => "48972151631",
                 "100000000000000"      => "529781669333",
                 "1000000000000000"     => "5693047157230",
                 "10000000000000000"    => "60832290450373",
                 "100000000000000000"   => "646862625625663",
                 "1000000000000000000"  => "6849459596884350",
                 "10000000000000000000" => "72259172519243461",
                },

        '12' => {
                 "10000000"             => "11068",
                 "100000000"            => "133862",
                 "1000000000"           => "1563465",
                 "10000000000"          => "17836903",
                 "100000000000"         => "200051717",
                 "1000000000000"        => "2214357712",
                 "10000000000000"       => "24255601105",
                 "100000000000000"      => "263439785143",
                 "1000000000000000"     => "2841076717752",
                 "10000000000000000"    => "30457549169277",
                 "100000000000000000"   => "324855769153426",
                 "1000000000000000000"  => "3449587218984911",
                 "10000000000000000000" => "36489283363168885",
                },

        '13' => {
                 "100000000"          => "63724",
                 "1000000000"         => "751610",
                 "10000000000"        => "8641282",
                 "100000000000"       => "97493048",
                 "1000000000000"      => "1084343921",
                 "10000000000000"     => "11926066887",
                 "100000000000000"    => "129986121851",
                 "1000000000000000"   => "1406214194147",
                 "10000000000000000"  => "15117458069816",
                 "100000000000000000" => "161651958148243",
                },

        '14' => {
                 "100000000"          => "30143",
                 "1000000000"         => "359812",
                 "10000000000"        => "4167745",
                 "100000000000"       => "47294032",
                 "1000000000000"      => "528497496",
                 "10000000000000"     => "5835244859",
                 "100000000000000"    => "63809981451",
                 "1000000000000000"   => "692279429615",
                 "10000000000000000"  => "7461064471468",
                 "100000000000000000" => "79961411383369",
                },

        '15' => {
                 "100000000"          => "14221",
                 "1000000000"         => "171396",
                 "10000000000"        => "2002277",
                 "100000000000"       => "22864432",
                 "1000000000000"      => "256721609",
                 "10000000000000"     => "2845415088",
                 "100000000000000"    => "31214953362",
                 "1000000000000000"   => "339576381990",
                 "10000000000000000"  => "3668450035737",
                 "100000000000000000" => "39397266129801",
                },

        '16' => {
                 "100000000"          => "6644",
                 "1000000000"         => "81378",
                 "10000000000"        => "959377",
                 "100000000000"       => "11023759",
                 "1000000000000"      => "124381518",
                 "10000000000000"     => "1384019728",
                 "100000000000000"    => "15231822577",
                 "1000000000000000"   => "166147634770",
                 "10000000000000000"  => "1799025041767",
                 "100000000000000000" => "19359235812437",
                },

        '17' => {
                 "1000000000"         => "38537",
                 "10000000000"        => "458176",
                 "100000000000"       => "5301868",
                 "1000000000000"      => "60136853",
                 "10000000000000"     => "671874910",
                 "100000000000000"    => "7418588349",
                 "1000000000000000"   => "81141472649",
                 "10000000000000000"  => "880608939264",
                 "100000000000000000" => "9494920297227",
                },

        '18' => {
                 "10000000000"         => "218163",
                 "100000000000"        => "2544515",
                 "1000000000000"       => "29020610",
                 "10000000000000"      => "325615879",
                 "100000000000000"     => "3607646060",
                 "1000000000000000"    => "39569497036",
                 "10000000000000000"   => "430444178496",
                 "100000000000000000"  => "4650394499994",
                 "1000000000000000000" => "49950199374227",
                },

        '19' => {
                 "10000000000"          => "103657",
                 "100000000000"         => "1218224",
                 "1000000000000"        => "13978352",
                 "10000000000000"       => "157569342",
                 "100000000000000"      => "1752071168",
                 "1000000000000000"     => "19273117219",
                 "10000000000000000"    => "210163154940",
                 "100000000000000000"   => "2275177007273",
                 "1000000000000000000"  => "24480596954105",
                 "10000000000000000000" => "262036734664892",
                },

        '20' => {
                 "10000000000"          => "49032",
                 "100000000000"         => "581979",
                 "1000000000000"        => "6722135",
                 "10000000000000"       => "76139019",
                 "100000000000000"      => "849839564",
                 "1000000000000000"     => "9377172857",
                 "10000000000000000"    => "102511166598",
                 "100000000000000000"   => "1112106715395",
                 "1000000000000000000"  => "11987644585115",
                 "10000000000000000000" => "128513242515056",
                },
    };

    my $v = _big2uistr($$from) // return ZERO;

    if (exists($pi_k_lookup->{$k}) and defined(my $count = $pi_k_lookup->{$k}{$v})) {
        return _set_int($count);
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _set_int(Math::Prime::Util::almost_prime_count($k, Math::GMPz::Rmpz_get_ui($n)));
    }

    my $count = Math::GMPz::Rmpz_init_set_ui(0);
    state $t = Math::GMPz::Rmpz_init_nobless();

    sub {
        my ($m, $p, $k, $j) = @_;

        Math::GMPz::Rmpz_div($t, $n, $m);
        Math::GMPz::Rmpz_root($t, $t, $k);

        Math::GMPz::Rmpz_fits_ulong_p($t) || die "Too large value!";
        my $hi = Math::GMPz::Rmpz_get_ui($t);

        if ($k == 2) {

            foreach my $q (@{_primes($p, $hi)}) {

                Math::GMPz::Rmpz_mul_ui($t, $m, $q);
                Math::GMPz::Rmpz_div($t, $n, $t);

                my $w  = Math::GMPz::Rmpz_fits_ulong_p($t)        ? Math::GMPz::Rmpz_get_ui($t)        : Math::GMPz::Rmpz_get_str($t, 10);
                my $pi = (HAS_PRIME_UTIL and $w < PRIMECOUNT_MIN) ? Math::Prime::Util::prime_count($w) : _prime_count($w);

                if (FAST_MODE and $pi < ULONG_MAX) {
                    Math::GMPz::Rmpz_add_ui($count, $count, $pi - $j);
                }
                else {
                    Math::GMPz::Rmpz_set_str($t, "$pi", 10);
                    Math::GMPz::Rmpz_sub_ui($t, $t, $j);
                    Math::GMPz::Rmpz_add($count, $count, $t);
                }

                ++$j;
            }

            return;
        }

        for (my $q = $p ; $q <= $hi ; $q = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($q) : Math::Prime::Util::GMP::next_prime($q))) {
            __SUB__->($m * $q, $q, $k - 1, $j++);
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 0);

    bless \$count;
}

*pi_k           = \&almost_prime_count;
*almost_primepi = \&almost_prime_count;

sub bigomega_inverse_len {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->almost_prime_count($from, $to);
}

sub almost_prime_sum {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->almost_prime_sum($to)->sub($k->almost_prime_sum($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_sum;
    }

    my $n = _any2mpz($$from) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    if ($k == 0) {
        return ONE;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my $total = Math::GMPz::Rmpz_init_set_ui(0);

    sub {
        my ($m, $p, $k, $j) = @_;

        my $s = do {
            Math::GMPz::Rmpz_div($t, $n, $m);
            Math::GMPz::Rmpz_root($t, $t, $k);
            Math::GMPz::Rmpz_get_ui($t);
        };

        if ($k == 2) {

            foreach my $q (@{_primes($p, $s)}) {

                Math::GMPz::Rmpz_mul_ui($t, $m, $q);
                Math::GMPz::Rmpz_div($u, $n, $t);

                my $w  = Math::GMPz::Rmpz_fits_ulong_p($u)      ? Math::GMPz::Rmpz_get_ui($u)       : Math::GMPz::Rmpz_get_str($u, 10);
                my $ps = (HAS_PRIME_UTIL and $w < PRIMESUM_MIN) ? Math::Prime::Util::sum_primes($w) : ${_set_int($w)->primes_sum};

                if (FAST_MODE and $ps < ULONG_MAX) {
                    Math::GMPz::Rmpz_addmul_ui($total, $t, $ps - $j);
                }
                else {
                    Math::GMPz::Rmpz_set_str($u, "$ps", 10);
                    Math::GMPz::Rmpz_sub_ui($u, $u, $j);
                    Math::GMPz::Rmpz_addmul($total, $u, $t);
                }

                $j += $q;
            }

            return;
        }

        while ($p <= $s) {
            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));
            __SUB__->($m * $p, $p, $k - 1, $j);
            $j += $p;
            $p = $r;
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 0);

    bless \$total;
}

sub bigomega_inverse_sum {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->almost_prime_sum($from, $to);
}

sub squarefree_almost_prime_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->squarefree_almost_prime_count($to)->sub($k->squarefree_almost_prime_count($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_count;
    }

    my $n = _any2mpz($$from) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    if ($k == 0) {
        return ONE;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp($n, _cached_pn_primorial($k)) < 0) {
        return ZERO;
    }

    state $t     = Math::GMPz::Rmpz_init_nobless();
    state $count = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($count, 0);

    sub {
        my ($m, $p, $k, $j) = @_;

        Math::GMPz::Rmpz_div($t, $n, $m);
        Math::GMPz::Rmpz_root($t, $t, $k);

        Math::GMPz::Rmpz_fits_ulong_p($t) || die "Too large value!";
        my $hi = Math::GMPz::Rmpz_get_ui($t);

        if ($k == 2) {

            foreach my $q (@{_primes($p, $hi)}) {

                Math::GMPz::Rmpz_mul_ui($t, $m, $q);
                Math::GMPz::Rmpz_div($t, $n, $t);

                my $w  = Math::GMPz::Rmpz_fits_ulong_p($t)        ? Math::GMPz::Rmpz_get_ui($t)        : Math::GMPz::Rmpz_get_str($t, 10);
                my $pi = (HAS_PRIME_UTIL and $w < PRIMECOUNT_MIN) ? Math::Prime::Util::prime_count($w) : _prime_count($w);

                if (FAST_MODE and $pi < ULONG_MAX) {
                    Math::GMPz::Rmpz_add_ui($count, $count, $pi - $j);
                }
                else {
                    Math::GMPz::Rmpz_set_str($t, "$pi", 10);
                    Math::GMPz::Rmpz_sub_ui($t, $t, $j);
                    Math::GMPz::Rmpz_add($count, $count, $t);
                }

                ++$j;
            }

            return;
        }

        for (; $p <= $hi ; ++$j) {
            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));
            __SUB__->($m * $p, $r, $k - 1, $j + 1);
            $p = $r;
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 1);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($count)
      ? Math::GMPz::Rmpz_get_ui($count)
      : Math::GMPz::Rmpz_init_set($count);

    bless \$r2;
}

*squarefree_pi_k           = \&squarefree_almost_prime_count;
*squarefree_almost_primepi = \&squarefree_almost_prime_count;

sub squarefree_semiprime_count {
    (TWO)->squarefree_almost_prime_count(@_);
}

*squarefree_semiprimes_count = \&squarefree_semiprime_count;

sub squarefree_semiprime_sum {
    (TWO)->squarefree_almost_prime_sum(@_);
}

*squarefree_semiprimes_sum = \&squarefree_semiprime_sum;

sub squarefree_almost_prime_sum {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->squarefree_almost_prime_sum($to)->sub($k->squarefree_almost_prime_sum($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_sum;
    }

    my $n = _any2mpz($$from) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    if ($k == 0) {
        return ONE;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp($n, _cached_pn_primorial($k)) < 0) {
        return ZERO;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my $total = Math::GMPz::Rmpz_init_set_ui(0);

    sub {
        my ($m, $p, $k, $j) = @_;

        my $s = do {
            Math::GMPz::Rmpz_div($t, $n, $m);
            Math::GMPz::Rmpz_root($t, $t, $k);
            Math::GMPz::Rmpz_get_ui($t);
        };

        if ($k == 2) {

            foreach my $q (@{_primes($p, $s)}) {

                $j += $q;
                Math::GMPz::Rmpz_mul_ui($t, $m, $q);
                Math::GMPz::Rmpz_div($u, $n, $t);

                my $w  = Math::GMPz::Rmpz_fits_ulong_p($u)      ? Math::GMPz::Rmpz_get_ui($u)       : Math::GMPz::Rmpz_get_str($u, 10);
                my $ps = (HAS_PRIME_UTIL and $w < PRIMESUM_MIN) ? Math::Prime::Util::sum_primes($w) : ${_set_int($w)->primes_sum};

                if (FAST_MODE and $ps < ULONG_MAX) {
                    Math::GMPz::Rmpz_addmul_ui($total, $t, $ps - $j);
                }
                else {
                    Math::GMPz::Rmpz_set_str($u, "$ps", 10);
                    Math::GMPz::Rmpz_sub_ui($u, $u, $j);
                    Math::GMPz::Rmpz_addmul($total, $u, $t);
                }
            }

            return;
        }

        while ($p <= $s) {
            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));
            $j += $p;
            __SUB__->($m * $p, $r, $k - 1, $j);
            $p = $r;
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 0);

    bless \$total;
}

sub omega_prime_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {

        # TODO: implement k-omega prime counting in a given range and use it when |from-to| < sqrt(to).
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->omega_prime_count($to)->sub($k->omega_prime_count($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_power_count;
    }

    my $n = _any2mpz($$from) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    if ($k == 0) {
        return ONE;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp($n, _cached_pn_primorial($k)) < 0) {
        return ZERO;
    }

    if ((HAS_PRIME_UTIL or (HAS_PRIME_UTIL and $k < 15)) and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _set_int(Math::Prime::Util::omega_prime_count($k, Math::GMPz::Rmpz_get_ui($n)));
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    my $count = Math::GMPz::Rmpz_init_set_ui(0);

    sub {
        my ($m, $p, $k, $j, $s) = @_;

        $s //= do {
            Math::GMPz::Rmpz_div($t, $n, $m);
            Math::GMPz::Rmpz_root($t, $t, $k);
            Math::GMPz::Rmpz_get_ui($t);
        };

        if ($k == 2) {

            for (; $p <= $s ; ++$j) {

                my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));

                for (Math::GMPz::Rmpz_mul_ui($v, $m, $p) ; Math::GMPz::Rmpz_cmp($v, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($v, $v, $p)) {

                    Math::GMPz::Rmpz_div($t, $n, $v);

                    if (Math::GMPz::Rmpz_cmp_ui($t, $r) < 0) {
                        last;
                    }

                    my $w  = Math::GMPz::Rmpz_fits_ulong_p($t)        ? Math::GMPz::Rmpz_get_ui($t)        : Math::GMPz::Rmpz_get_str($t, 10);
                    my $pi = (HAS_PRIME_UTIL and $w < PRIMECOUNT_MIN) ? Math::Prime::Util::prime_count($w) : _prime_count($w);

                    if (FAST_MODE and $pi < ULONG_MAX) {
                        Math::GMPz::Rmpz_add_ui($count, $count, $pi - $j);
                    }
                    else {
                        Math::GMPz::Rmpz_set_str($t, "$pi", 10);
                        Math::GMPz::Rmpz_sub_ui($t, $t, $j);
                        Math::GMPz::Rmpz_add($count, $count, $t);
                    }

                    for (my $r2 = $r ; $r2 <= $w ; $r2 = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($r2) : Math::Prime::Util::GMP::next_prime($r2))) {

                        Math::GMPz::Rmpz_mul_ui($u, $v, $r2);
                        Math::GMPz::Rmpz_mul_ui($u, $u, $r2);

                        if (Math::GMPz::Rmpz_cmp($u, $n) > 0) {
                            last;
                        }

                        my $i = 0;
                        for (; Math::GMPz::Rmpz_cmp($u, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($u, $u, $r2)) {
                            ++$i;
                        }
                        Math::GMPz::Rmpz_add_ui($count, $count, $i);
                    }
                }

                $p = $r;
            }

            return;
        }

        for (; $p <= $s ; ++$j) {

            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));

            for (my $w = $m * $p ; Math::GMPz::Rmpz_cmp($w, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($w, $w, $p)) {

                Math::GMPz::Rmpz_div($t, $n, $w);
                Math::GMPz::Rmpz_root($t, $t, $k - 1);

                my $s = Math::GMPz::Rmpz_get_ui($t);
                last if ($r > $s);
                __SUB__->($w, $r, $k - 1, $j + 1, $s);
            }

            $p = $r;
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 1);

    bless \$count;
}

*omega_primepi = \&omega_prime_count;

sub omega_inverse_len {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->omega_prime_count($from, $to);
}

sub omega_prime_sum {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->omega_prime_sum($to)->sub($k->omega_prime_sum($from->dec));
    }

    $k = _any2ui($$k) // return ZERO;

    if ($k == 1) {
        return $_[1]->prime_power_sum;
    }

    my $n = _any2mpz($$from) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    if ($k == 0) {
        return ONE;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp($n, _cached_pn_primorial($k)) < 0) {
        return ZERO;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    my $total = Math::GMPz::Rmpz_init_set_ui(0);

    sub {
        my ($m, $p, $k, $j, $s) = @_;

        $s //= do {
            Math::GMPz::Rmpz_div($t, $n, $m);
            Math::GMPz::Rmpz_root($t, $t, $k);
            Math::GMPz::Rmpz_get_ui($t);
        };

        if ($k == 2) {

            while ($p <= $s) {

                $j += $p;
                my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));

                for (Math::GMPz::Rmpz_mul_ui($v, $m, $p) ; Math::GMPz::Rmpz_cmp($v, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($v, $v, $p)) {

                    Math::GMPz::Rmpz_div($u, $n, $v);

                    if (Math::GMPz::Rmpz_cmp_ui($u, $r) < 0) {
                        last;
                    }

                    my $w  = Math::GMPz::Rmpz_fits_ulong_p($u)      ? Math::GMPz::Rmpz_get_ui($u)       : Math::GMPz::Rmpz_get_str($u, 10);
                    my $ps = (HAS_PRIME_UTIL and $w < PRIMESUM_MIN) ? Math::Prime::Util::sum_primes($w) : ${_set_int($w)->primes_sum};

                    if (FAST_MODE and $ps < ULONG_MAX) {
                        Math::GMPz::Rmpz_addmul_ui($total, $v, $ps - $j);
                    }
                    else {
                        Math::GMPz::Rmpz_set_str($u, "$ps", 10);
                        Math::GMPz::Rmpz_sub_ui($u, $u, $j);
                        Math::GMPz::Rmpz_addmul($total, $u, $v);
                    }

                    for (my $r2 = $r ; $r2 <= $w ; $r2 = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($r2) : Math::Prime::Util::GMP::next_prime($r2))) {

                        Math::GMPz::Rmpz_mul_ui($u, $v, $r2);
                        Math::GMPz::Rmpz_mul_ui($u, $u, $r2);

                        if (Math::GMPz::Rmpz_cmp($u, $n) > 0) {
                            last;
                        }

                        for (; Math::GMPz::Rmpz_cmp($u, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($u, $u, $r2)) {
                            Math::GMPz::Rmpz_add($total, $total, $u);
                        }
                    }
                }

                $p = $r;
            }

            return;
        }

        while ($p <= $s) {

            $j += $p;
            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));

            for (my $w = $m * $p ; Math::GMPz::Rmpz_cmp($w, $n) <= 0 ; Math::GMPz::Rmpz_mul_ui($w, $w, $p)) {

                Math::GMPz::Rmpz_div($t, $n, $w);
                Math::GMPz::Rmpz_root($t, $t, $k - 1);

                my $s = Math::GMPz::Rmpz_get_ui($t);
                last if ($r > $s);
                __SUB__->($w, $r, $k - 1, $j, $s);
            }

            $p = $r;
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, 0);

    bless \$total;
}

sub omega_inverse_sum {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->omega_prime_sum($from, $to);
}

sub nth_omega_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 1 or goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->nth_prime_power;
    }

    my $k_obj = bless \$k;
    my $n_obj = $n;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) > 0 or do {
        return ONE if (Math::GMPz::Rmpz_sgn($n) == 0);    # not k-omega prime, but...
        goto &nan;
    };

    my $min = Math::GMPz::Rmpz_init();
    my $max = Math::GMPz::Rmpz_init_set($n);

    Math::GMPz::Rmpz_set($min, _cached_pn_primorial($k));
    Math::GMPz::Rmpz_mul_2exp($max, $min, 1);

    while (__cmp__(${$k_obj->omega_prime_count(bless \$max)}, $n) < 0) {
        Math::GMPz::Rmpz_set($min, $max);
        Math::GMPz::Rmpz_mul_ui($max, $max, 2);
    }

    if ((HAS_PRIME_UTIL or (HAS_PRIME_UTIL and $k <= 12)) and Math::GMPz::Rmpz_fits_ulong_p($max)) {
        my $r = Math::Prime::Util::nth_omega_prime($k, Math::GMPz::Rmpz_get_ui($n));
        if ($r) {
            return _set_int("$r");
        }
    }

    my $v     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init();

    while (1) {
        Math::GMPz::Rmpz_add($v, $min, $max);
        Math::GMPz::Rmpz_div_2exp($v, $v, 1);

        $count =
          ((HAS_PRIME_UTIL or (HAS_PRIME_UTIL and $k < 15)) and Math::GMPz::Rmpz_fits_ulong_p($v))
          ? Math::GMPz::Rmpz_init_set_ui(Math::Prime::Util::omega_prime_count($k, Math::GMPz::Rmpz_get_ui($v)))
          : ${$k_obj->omega_prime_count(bless \$v)};

        my $cmp = __cmp__($count, $n);

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($max, $v, 1);
        }
        elsif ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($min, $v, 1);
        }
        else {
            last;
        }
    }

    $k_obj->omega_primes((bless \$min), (bless \$v))->last;
}

sub next_omega_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->next_prime_power;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set(_cached_pn_primorial($k));

    # The smallest k-omega prime is primorial(p_k)
    if (Math::GMPz::Rmpz_cmp($n, $r) < 0) {
        return bless \$r;
    }

    if ($k <= 7 or $n > $r * ($k * $k)) {

        # Optimization for native integers
        if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_slong_p($n)) {
            $n = Math::GMPz::Rmpz_get_ui($n) + 1;
            until (Math::Prime::Util::is_omega_prime($k, $n)) {
                ++$n;
            }
            return _set_int($n);
        }

        Math::GMPz::Rmpz_add_ui($r, $n, 1);

        my $r_obj = bless \$r;

        until ($r_obj->is_omega_prime($k_obj)) {
            Math::GMPz::Rmpz_add_ui($r, $r, 1);
        }

        return $r_obj;
    }

    $k_obj->omega_prime_count($n_obj)->inc->nth_omega_prime($k_obj);
}

sub prev_omega_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->prev_prime_power;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set(_cached_pn_primorial($k));

    # The smallest k-omega prime is primorial(p_k)
    if (Math::GMPz::Rmpz_cmp($n, $r) <= 0) {
        goto &nan;
    }

    if ($k <= 7 or $n > $r * ($k * $k)) {

        # Optimization for native integers
        if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
            $n = Math::GMPz::Rmpz_get_ui($n) - 1;
            until (Math::Prime::Util::is_omega_prime($k, $n)) {
                --$n;
            }
            return bless \$n;
        }

        Math::GMPz::Rmpz_sub_ui($r, $n, 1);

        my $r_obj = bless \$r;

        until ($r_obj->is_omega_prime($k_obj)) {
            Math::GMPz::Rmpz_sub_ui($r, $r, 1);
        }

        return $r_obj;
    }

    my $count = $k_obj->omega_prime_count($n_obj);
    $count = $count->dec if $n_obj->is_omega_prime($k_obj);
    $count->nth_omega_prime($k_obj);
}

sub nth_squarefree_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 1 or goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->nth_prime;
    }

    my $k_obj = _set_int($k);
    my $n_obj = $n;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) > 0 or do {
        return ONE if (Math::GMPz::Rmpz_sgn($n) == 0);    # not k-almost prime, but...
        goto &nan;
    };

    my $min = Math::GMPz::Rmpz_init();
    my $max = Math::GMPz::Rmpz_init_set($n);

    Math::GMPz::Rmpz_set($min, _cached_pn_primorial($k));
    Math::GMPz::Rmpz_mul_2exp($max, $min, 1);

    while (__cmp__(${$k_obj->squarefree_almost_prime_count(bless \$max)}, $n) < 0) {
        Math::GMPz::Rmpz_set($min, $max);
        Math::GMPz::Rmpz_mul_ui($max, $max, 2);
    }

    my $v     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init();

    while (1) {
        Math::GMPz::Rmpz_add($v, $min, $max);
        Math::GMPz::Rmpz_div_2exp($v, $v, 1);

        $count = ${$k_obj->squarefree_almost_prime_count(bless \$v)};

        my $cmp = __cmp__($count, $n);

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($max, $v, 1);
        }
        elsif ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($min, $v, 1);
        }
        else {
            last;
        }
    }

    $k_obj->squarefree_almost_primes((bless \$min), (bless \$v))->last;
}

sub next_squarefree_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->next_prime;
    }
    elsif ($k == 2) {
        return $n->next_squarefree_semiprime;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set(_cached_pn_primorial($k));

    # The smallest squarefree k-almost prime is primorial(p_k)
    if (Math::GMPz::Rmpz_cmp($n, $r) < 0) {
        return bless \$r;
    }

    if ($k <= 7 or $n > $r * ($k * $k)) {

        # Optimization for native integers
        if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_slong_p($n)) {
            $n = Math::GMPz::Rmpz_get_ui($n) + 1;
            until (Math::Prime::Util::is_almost_prime($k, $n) and Math::Prime::Util::is_square_free($n)) {
                ++$n;
            }
            return bless \$n;
        }

        Math::GMPz::Rmpz_add_ui($r, $n, 1);

        my $r_obj = bless \$r;

        until ($r_obj->is_squarefree_almost_prime($k_obj)) {
            Math::GMPz::Rmpz_add_ui($r, $r, 1);
        }

        return $r_obj;
    }

    $k_obj->squarefree_almost_prime_count($n_obj)->inc->nth_squarefree_almost_prime($k_obj);
}

sub prev_squarefree_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->prev_prime;
    }
    elsif ($k == 2) {
        return $n->prev_squarefree_semiprime;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set(_cached_pn_primorial($k));

    # The smallest squarefree k-almost prime is primorial(p_k)
    if (Math::GMPz::Rmpz_cmp($n, $r) <= 0) {
        goto &nan;
    }

    if ($k <= 7 or $n > $r * ($k * $k)) {

        # Optimization for native integers
        if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
            $n = Math::GMPz::Rmpz_get_ui($n) - 1;
            until (Math::Prime::Util::is_almost_prime($k, $n) and Math::Prime::Util::is_square_free($n)) {
                --$n;
            }
            return bless \$n;
        }

        Math::GMPz::Rmpz_sub_ui($r, $n, 1);

        my $r_obj = bless \$r;

        until ($r_obj->is_squarefree_almost_prime($k_obj)) {
            Math::GMPz::Rmpz_sub_ui($r, $r, 1);
        }

        return $r_obj;
    }

    my $count = $k_obj->squarefree_almost_prime_count($n_obj);
    $count = $count->dec if $n_obj->is_squarefree_almost_prime($k_obj);
    $count->nth_squarefree_almost_prime($k_obj);
}

sub nth_sphenic {
    my ($n) = @_;
    $n->nth_squarefree_almost_prime(THREE);
}

sub next_sphenic {
    my ($n) = @_;
    $n->next_squarefree_almost_prime(THREE);
}

sub prev_sphenic {
    my ($n) = @_;
    $n->prev_squarefree_almost_prime(THREE);
}

sub prime_power_count {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $x = _big2istr($$x) // return ZERO;
        $x = 2 if $x < 2;
        $y = _big2uistr($$y) // return ZERO;
    }
    else {
        $y = _big2uistr($$x) // return ZERO;
        $x = 2;
    }

    if (HAS_PRIME_UTIL and $x == 2 and $y < PRIMECOUNT_MIN) {
        return _set_int(Math::Prime::Util::prime_power_count($y));
    }

    # Support for large integers
    if ($y >= ~0) {

        $x = Math::GMPz::Rmpz_init_set_str("$x", 10);
        $y = Math::GMPz::Rmpz_init_set_str("$y", 10);

        if ($y - $x > 1e6) {

            if ($x == 2) {

                my $pp_count = ZERO;
                my $ilog2    = __ilog__($y, 2);

                for (my $k = 1 ; $k <= $ilog2 ; ++$k) {
                    my $root = Math::Prime::Util::GMP::rootint($y, $k);
                    $pp_count = $pp_count->add(_set_int(_prime_count($root)));
                }

                return $pp_count;
            }

            my $x_pp_count = _set_int($x - 1)->prime_power_count;
            my $y_pp_count = _set_int($y)->prime_power_count;

            my $pp_count = $y_pp_count->sub($x_pp_count);

            return $pp_count;
        }

        my $count = 0;

        for (; Math::GMPz::Rmpz_cmp($x, $y) <= 0 ; Math::GMPz::Rmpz_add_ui($x, $x, 1)) {
            ++$count if Math::Prime::Util::GMP::is_prime_power(Math::GMPz::Rmpz_get_str($x, 10));
        }

        return _set_int($count);
    }

    return ZERO if ($y < $x);

#<<<
    state $pp_table = [ 0,  0,  1,  2,  3,  4,  4,  5,  6,  7,  7,  8,  8,  9,  9,  9, 10, 11, 11, 12, 12,
                       12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20,
                       20, 21, 21, 21, 21, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 25, 25, 26, 26,
                       26, 27, 27, 27, 28, 28, 28, 28, 29, 29, 30, 30, 30, 30, 30, 30, 31, 31, 32, 32, 33,
                       33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35];

    state $pi_table = [ 0,  0,  1,  2,  2,  3,  3,  4,  4,  4,  4,  5,  5,  6,  6,  6,  6,  7,  7,  8,  8,  8,
                        8,  9,  9,  9,  9,  9,  9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14,
                       14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18,
                       18, 19, 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 23,
                       23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25];
#>>>

    # Optimization for narrow ranges
    if ($y - $x <= 100 or "$x" / "$y" >= 0.999) {

        if ($x <= 2 and $y <= 100) {
            return _set_int($pp_table->[$y]);
        }

        my $count = 0;

        for (; $x <= $y ; ++$x) {
            ++$count if Math::Prime::Util::GMP::is_prime_power($x);
        }

        return ZERO if ($count == 0);
        return ONE  if ($count == 1);

        return _set_int($count);
    }

    my $pp_count = sub {
        my ($n) = @_;

        return $pp_table->[$n] if $n <= 100;

        if (HAS_PRIME_UTIL and $n < PRIMECOUNT_MIN) {
            return Math::Prime::Util::prime_power_count($n);
        }

        my $count = 0;

        foreach my $k (1 .. (HAS_PRIME_UTIL ? Math::Prime::Util::logint($n, 2) : Math::Prime::Util::GMP::logint($n, 2))) {
            my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::rootint($n, $k) : Math::Prime::Util::GMP::rootint($n, $k));

            if ($r <= 100) {
                $count += $pi_table->[$r];
            }
            else {
                $count += (HAS_PRIME_UTIL and $r < PRIMECOUNT_MIN) ? Math::Prime::Util::prime_count($r) : _prime_count($r);
            }
        }

        $count;
    };

    my $x_pp_count = ($x == 2 ? 0 : $pp_count->($x - 1));
    my $y_pp_count = $pp_count->($y);

    my $count = $y_pp_count - $x_pp_count;

    bless \$count;
}

sub prime_power_sum {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->prime_power_sum->sub($from->dec->prime_power_sum);
    }

    # Simple formula:
    #   prime_power_sum(n) = Sum_{k=1..floor(log_2(n))} Sum_{p prime <= n^(1/k)} p^k

    # Optimized formula:
    #   a(n) = Sum_{p prime <= n} p
    #   b(n) = Sum_{p prime <= n^(1/2)} p^2
    #   c(n) = Sum_{p prime <= n^(1/3)} f(p)

    # prime_power_sum(n) = a(n) + b(n) + c(n)

    my $n = _big2pistr($$from) // return ZERO;

    my $sr = Math::Prime::Util::GMP::sqrtint($n);
    my $cr = Math::Prime::Util::GMP::rootint($n, 3);

    my $ps1 = _set_int($n)->primes_sum;
    my $ps2 = (TWO)->primes_sum(_set_int($sr), TWO);

    state $u = Math::GMPz::Rmpz_init_nobless();

    my $z  = Math::GMPz::Rmpz_init_set_str("$n", 10);
    my $pp = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $p (@{_primes(2, $cr)}) {

        # f(p) = sum(3..n.ilog(p), {|k| p**k })
        #      = (p**(n.ilog(p)+1) - 1)/(p-1) - p**2 - p - 1

        my $l = __ilog__($z, $p);
        Math::GMPz::Rmpz_ui_pow_ui($u, $p, $l + 1);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);
        Math::GMPz::Rmpz_divexact_ui($u, $u, $p - 1);
        if (FAST_MODE and $p * $p + $p + 1 < ULONG_MAX) {
            Math::GMPz::Rmpz_sub_ui($u, $u, $p * $p + $p + 1);
        }
        else {    # for very large n (>10^28 on 64-bit and >10^14 on 32-bit)
            my $t = Math::GMPz::Rmpz_init_set_ui($p);
            Math::GMPz::Rmpz_mul_ui($t, $t, $p);
            Math::GMPz::Rmpz_add_ui($t, $t, $p + 1);
            Math::GMPz::Rmpz_sub($u, $u, $t);
        }
        Math::GMPz::Rmpz_add($pp, $pp, $u);
    }

    $ps1->add($ps2)->add(bless \$pp);
}

*prime_powers_sum = \&prime_power_sum;

sub nth_prime {
    my ($n) = @_;

    $n = $$n;
    $n = (ref($n) ? _any2mpz($n) : $n) // goto &nan;
    $n < 0 and goto &nan;

    if ($n == 0) {
        return ONE;    # not a prime, but may be convenient...
    }

    state $nth_prime_lookup = {

        # (10^n)-th prime.
        # OEIS: https://oeis.org/A006988
        "10000000"                   => "179424673",
        "100000000"                  => "2038074743",
        "1000000000"                 => "22801763489",
        "10000000000"                => "252097800623",
        "100000000000"               => "2760727302517",
        "1000000000000"              => "29996224275833",
        "10000000000000"             => "323780508946331",
        "100000000000000"            => "3475385758524527",
        "1000000000000000"           => "37124508045065437",
        "10000000000000000"          => "394906913903735329",
        "100000000000000000"         => "4185296581467695669",
        "1000000000000000000"        => "44211790234832169331",
        "10000000000000000000"       => "465675465116607065549",
        "100000000000000000000"      => "4892055594575155744537",
        "1000000000000000000000"     => "51271091498016403471853",
        "10000000000000000000000"    => "536193870744162118627429",
        "100000000000000000000000"   => "5596564467986980643073683",
        "1000000000000000000000000"  => "58310039994836584070534263",
        "10000000000000000000000000" => "606527267811189857426370533",

        # (2^n)-th prime
        # OEIS: https://oeis.org/A033844
        "1073741824"               => "24563311309",
        "2147483648"               => "50685770167",
        "4294967296"               => "104484802057",
        "8589934592"               => "215187847711",
        "17179869184"              => "442795487221",
        "34359738368"              => "910399916939",
        "68719476736"              => "1870358526653",
        "137438953472"             => "3839726846311",
        "274877906944"             => "7877263558621",
        "549755813888"             => "16149760533341",
        "1099511627776"            => "33089240375501",
        "2199023255552"            => "67756520645329",
        "4398046511104"            => "138666449011757",
        "8796093022208"            => "283634652716357",
        "17592186044416"           => "579863159340527",
        "35184372088832"           => "1184895616861903",
        "70368744177664"           => "2420094683001859",
        "140737488355328"          => "4940729268330643",
        "281474976710656"          => "10082409897709157",
        "562949953421312"          => "20566476729238691",
        "1125899906842624"         => "41935796950796653",
        "2251799813685248"         => "85476377250109733",
        "4503599627370496"         => "174160587542317721",
        "9007199254740992"         => "354733509412061993",
        "18014398509481984"        => "722285281729443799",
        "36028797018963968"        => "1470194760556507397",
        "72057594037927936"        => "2991614170035124397",
        "144115188075855872"       => "6085631874569939777",
        "288230376151711744"       => "12375982557205846193",
        "576460752303423488"       => "25161232392544176197",
        "1152921504606846976"      => "51140670371058101123",
        "2305843009213693952"      => "103917116257220706127",
        "4611686018427387904"      => "211104554420210305087",
        "9223372036854775808"      => "428747374788279617303",
        "18446744073709551616"     => "870566678511500413493",
        "36893488147419103232"     => "1767268299972575740723",
        "73786976294838206464"     => "3586789210729460889317",
        "147573952589676412928"    => "7278050150447936843717",
        "295147905179352825856"    => "14764978793012287880219",
        "590295810358705651712"    => "29947588495888738082431",
        "1180591620717411303424"   => "60730194035557507211347",
        "2361183241434822606848"   => "123129946710886829498713",
        "4722366482869645213696"   => "249598086801961825095881",
        "9444732965739290427392"   => "505870764273226657981427",
        "18889465931478580854784"  => "1025087216809475771050003",
        "37778931862957161709568"  => "2076859014052740233944627",
        "75557863725914323419136"  => "4207073961494759547984247",
        "151115727451828646838272" => "8520834035044766488749161",
        "302231454903657293676544" => "17254990129969542495182251",
                              };

    if (exists($nth_prime_lookup->{$n})) {
        return _set_int($nth_prime_lookup->{$n});
    }

    if ($n > 1_000_000) {

        if ($n > (PRIMECOUNT_MIN >> 6) and $USE_PRIMECOUNT) {
            my $p = `primecount -n $n`;
            if ($? == 0 and defined($p)) {
                chomp($p);
                if ($p) {    # make sure p is not zero
                    return _set_int($p);
                }
            }
        }

        if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
            if ((INTSIZE <= 32) ? ($n < 203280221) : 1) {
                return _set_int(Math::Prime::Util::nth_prime("$n"));
            }
        }

        my $min = _any2mpz(${_set_int($n)->nth_prime_lower});
        my $max = _any2mpz(${_set_int($n)->nth_prime_upper});

        if (Math::GMPz::Rmpz_fits_ulong_p($max)) {
            $min = Math::GMPz::Rmpz_get_ui($min);
            $max = Math::GMPz::Rmpz_get_ui($max);
        }

        my $k = 0;
        my $count;

        while (1) {
            $k =
              (HAS_PRIME_UTIL and $max < ULONG_MAX)
              ? Math::Prime::Util::divint(Math::Prime::Util::addint($min, $max), 2)
              : Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::addint($min, $max), 2);

            $count =
              (HAS_PRIME_UTIL and $k < PRIMECOUNT_MIN)
              ? Math::Prime::Util::prime_count($k)
              : _prime_count($k);

            if ($k > (ULONG_MAX >> 1)) {
                $k = _str2obj("$k");
            }

            if ($count > (ULONG_MAX >> 1)) {
                $count = _str2obj("$count");
            }

            if (CORE::abs($count - $n) <= CORE::int("$k"**(2 / 3))) {
                last;
            }

            my $cmp = $count <=> $n;

            if ($cmp > 0) {
                $max = $k - 1;
            }
            elsif ($cmp < 0) {
                $min = $k + 1;
            }
            else {
                last;
            }
        }

        if (!_is_prob_prime($k)) {
            $k = _prev_prime($k);
        }

        my $cmp = ($n <=> $count);
        while ($count != $n) {
            $k = ($cmp < 0) ? _prev_prime($k) : _next_prime($k);
            $count += $cmp;
        }

        return _set_int($k);
    }

    state @table;

    my $limit = 1000 + (2 * $n * CORE::int(CORE::log("$n")));
    $limit = 15_485_863 if $limit > 15_485_863;

    if (@table < $n) {
        $table[0] = 2;
        push @table, @{_primes($table[-1] + 1, $limit)};
    }

    _set_int($table[$n - 1]);
}

*prime = \&nth_prime;

sub nth_prime_power {
    my ($n) = @_;

    $n = $$n;
    $n = (ref($n) ? _any2mpz($n) : $n) // goto &nan;
    $n < 0 and goto &nan;

    return ONE   if ($n == 0);    # not a prime power, but...
    return TWO   if ($n == 1);
    return THREE if ($n == 2);

    # Lower and upper bounds
    my $min = $n;
    my $max = _nth_prime_upper(_big2uistr($n));

    if ($n > 1e6) {

        # Better bounds for the n-th prime power
        $min = _any2mpz(${_set_int($n)->nth_prime_power_lower});
        $max = _any2mpz(${_set_int($n)->nth_prime_power_upper});

        if (Math::GMPz::Rmpz_fits_ulong_p($max)) {
            $min = Math::GMPz::Rmpz_get_ui($min);
            $max = Math::GMPz::Rmpz_get_ui($max);
        }
    }

    if (HAS_PRIME_UTIL and $n < PRIMECOUNT_MIN and !ref($max)) {
        return _set_int(Math::Prime::Util::nth_prime_power($n));
    }

    my $k = 0;
    my $count;

    while (1) {
        $k =
          (HAS_PRIME_UTIL and $max < ULONG_MAX)
          ? Math::Prime::Util::divint(Math::Prime::Util::addint($min, $max), 2)
          : Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::addint($min, $max), 2);

        $count =
          (HAS_PRIME_UTIL and $k < PRIMECOUNT_MIN)
          ? Math::Prime::Util::prime_power_count($k)
          : ${_set_int($k)->prime_power_count};

        if ($k > (ULONG_MAX >> 1)) {
            $k = _str2obj("$k");
        }

        if ($count > (ULONG_MAX >> 1)) {
            $count = _str2obj("$count");
        }

        if (CORE::abs($count - $n) <= CORE::int(CORE::sqrt($k))) {
            last;
        }

        my $cmp = ($count <=> $n);

        if ($cmp > 0) {
            $max = $k - 1;
        }
        elsif ($cmp < 0) {
            $min = $k + 1;
        }
        else {
            last;
        }
    }

    until (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power($k) : Math::Prime::Util::GMP::is_prime_power($k)) {
        --$k;
    }

    my $cmp = ($n <=> $count);
    while ($count != $n) {
        do { $k += $cmp } until (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power($k) : Math::Prime::Util::GMP::is_prime_power($k));
        $count += $cmp;
    }

    _set_int($k);
}

sub composite_count {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->composite_count->sub($from->dec->composite_count);
    }

    my $n = _any2mpz($$from) // goto &nan;

    Math::GMPz::Rmpz_cmp_ui($n, 4) >= 0
      or return ZERO;

    my $pi = _set_int(_prime_count(Math::GMPz::Rmpz_get_str($n, 10)));
    bless \__dec__(__sub__($n, $$pi));    # n - pi(n) - 1
}

sub composite_sum {
    my ($from, $to, $k) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->composite_sum(undef, $k)->sub($from->dec->composite_sum(undef, $k));
    }

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
    }
    else {
        $k = 1;
    }

    if ($k == 0) {
        return $from->composite_count;
    }

    my $n = _any2mpz($$from) // goto &nan;

    Math::GMPz::Rmpz_cmp_ui($n, 4) >= 0
      or return ZERO;

    (bless \$n)->faulhaber_sum(bless \$k)->sub((TWO)->prime_sum((bless \$n), (bless \$k)))->dec;
}

*composites_sum = \&composite_sum;

sub nth_composite {
    my ($n) = @_;

    $n = $$n;
    $n = (ref($n) ? _any2mpz($n) : $n) // goto &nan;
    $n < 0 and goto &nan;

    return ONE         if ($n == 0);    # not composite, but...
    return _set_int(4) if ($n == 1);

    my ($min, $max);

    if ($n > 1e6) {

        # Better bounds for the n-th composite number
        $min = _any2mpz(${_set_int($n)->nth_composite_lower});
        $max = _any2mpz(${_set_int($n)->nth_composite_upper});

        if (Math::GMPz::Rmpz_fits_ulong_p($max)) {
            $min = Math::GMPz::Rmpz_get_ui($min);
            $max = Math::GMPz::Rmpz_get_ui($max);
        }
    }
    else {
        my $k = ref($n) ? _big2uistr($n) : $n;

        # Lower and upper bounds from A002808 (for n >= 4).
        $min = CORE::int($k + $k / CORE::log($k) + $k / (CORE::log($k)**2));
        $max = CORE::int($k + $k / CORE::log($k) + (3 * $k) / (CORE::log($k)**2));
    }

    if ($n < 4) {
        $min = 4;
        $max = 8;
    }

    my $k = 0;
    my $count;

    while (1) {

        $k =
          (HAS_PRIME_UTIL and $max < ULONG_MAX)
          ? Math::Prime::Util::divint(Math::Prime::Util::addint($min, $max), 2)
          : Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::addint($min, $max), 2);

        my $pi =
          (HAS_PRIME_UTIL and $k < PRIMECOUNT_MIN)
          ? Math::Prime::Util::prime_count($k)
          : _prime_count($k);

        if ($k > (ULONG_MAX >> 1)) {
            $k = _str2obj("$k");
        }

        if ($pi > (ULONG_MAX >> 1)) {
            $pi = _str2obj("$pi");
        }

        $count = $k - $pi - 1;

        if (CORE::abs($count - $n) <= CORE::int(CORE::sqrt($k))) {
            last;
        }

        my $cmp = $count <=> $n;

        if ($cmp > 0) {
            $max = $k - 1;
        }
        elsif ($cmp < 0) {
            $min = $k + 1;
        }
        else {
            last;
        }
    }

    if (_is_prob_prime($k)) {
        --$k;
    }

    my $cmp = ($n <=> $count);
    while ($count != $n) {
        do { $k += $cmp } while _is_prob_prime($k);
        $count += $cmp;
    }

    _set_int($k);
}

*composite = \&nth_composite;

sub nth_squarefree {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_fits_slong_p($n)
      or return ((bless \$n)->nth_powerfree(TWO));

    $n = _any2ui($n) // goto &nan;

    return ZERO if ($n == 0);    # not squarefree, but...
    return ONE  if ($n == 1);

    if (HAS_PRIME_UTIL) {
        my $r = Math::Prime::Util::nth_powerfree($n, 2);
        return bless \$r;
    }

    my $zeta2 = 1.64493406684822643647241516664603;
    my $k     = CORE::int($zeta2 * $n);

    my $count =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::powerfree_count($k, 2)
      : _native_squarefree_count($k);

    until (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($k) : Math::Prime::Util::GMP::moebius($k)) {
        --$k;
    }

    my $cmp = ($n <=> $count);
    while ($count != $n) {
        do { $k += $cmp } until (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($k) : Math::Prime::Util::GMP::moebius($k));
        $count += $cmp;
    }

    _set_int($k);
}

sub nth_powerfree {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 2;
    $n = _any2mpz($$n) // goto &nan;

    $k >= 2 or goto &nan;
    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;

    return ZERO if (Math::GMPz::Rmpz_sgn($n) == 0);         # not k-powerfree, but...
    return ONE  if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0);

    if ($k == 2 and Math::GMPz::Rmpz_fits_slong_p($n)) {
        return ((bless \$n)->nth_squarefree);
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_slong_p($n)) {
        my $r = Math::Prime::Util::nth_powerfree(Math::GMPz::Rmpz_get_ui($n), $k);
        return bless \$r;
    }

    _set_int(Math::Prime::Util::GMP::nth_powerfree(Math::GMPz::Rmpz_get_str($n, 10), $k));
}

sub nth_cubefree {
    my ($n) = @_;
    $n->nth_powerfree(THREE);
}

sub hilbert_symbol {
    my ($n, $k, $p) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);
    ref($p) eq __PACKAGE__ or _valid(\$p);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;
    $p = _any2mpz($$p) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) == 0 and return ZERO;
    Math::GMPz::Rmpz_sgn($k) == 0 and return ZERO;
    Math::GMPz::Rmpz_sgn($p) <= 0 and return ZERO;

    # Explicit valuation and Kronecker evaluation
    state $A = Math::GMPz::Rmpz_init_nobless();
    state $B = Math::GMPz::Rmpz_init_nobless();
    my $alpha = Math::GMPz::Rmpz_remove($A, $n, $p);
    my $beta  = Math::GMPz::Rmpz_remove($B, $k, $p);

    if (Math::GMPz::Rmpz_cmp_ui($p, 2) == 0) {
        my $A_mod8 = Math::GMPz::Rmpz_fdiv_ui($A, 8);
        my $B_mod8 = Math::GMPz::Rmpz_fdiv_ui($B, 8);

        my $eps_A = ($A_mod8 - 1) >> 1;
        my $eps_B = ($B_mod8 - 1) >> 1;
        my $omg_A = (($A_mod8 * $A_mod8 - 1) >> 3) & 1;
        my $omg_B = (($B_mod8 * $B_mod8 - 1) >> 3) & 1;

        my $pow = ($eps_A * $eps_B + $alpha * $omg_B + $beta * $omg_A) % 2;
        return $pow ? MONE : ONE;
    }

    my $p_mod4 = Math::GMPz::Rmpz_fdiv_ui($p, 4);
    my $eps_p  = ($p_mod4 - 1) >> 1;
    my $sgn    = (($alpha * $beta * $eps_p) % 2) ? -1 : 1;

    my $krA = Math::GMPz::Rmpz_jacobi($A, $p);
    my $krB = Math::GMPz::Rmpz_jacobi($B, $p);

    $krA = 1 if $beta % 2 == 0;
    $krB = 1 if $alpha % 2 == 0;

    my $res = $sgn * $krA * $krB;
    return (($res == -1) ? MONE : ONE);
}

*hilbert = \&hilbert_symbol;

sub legendre {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    my $s = Math::GMPz::Rmpz_legendre(_any2mpz($$x) // (goto &nan), _any2mpz($$y) // (goto &nan));
    $s ? (($s == 1) ? ONE : MONE) : ZERO;
}

*Legendre        = \&legendre;
*legendre_symbol = \&legendre;

sub jacobi {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    my $s = Math::GMPz::Rmpz_jacobi(_any2mpz($$x) // (goto &nan), _any2mpz($$y) // (goto &nan));
    $s ? (($s == 1) ? ONE : MONE) : ZERO;
}

*Jacobi        = \&jacobi;
*jacobi_symbol = \&jacobi;

sub kronecker {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    my $s;

    if (FAST_MODE and !ref($x)) {
        if (!ref($y)) {
            $s = (HAS_PRIME_UTIL ? Math::Prime::Util::kronecker($x, $y) : Math::Prime::Util::GMP::kronecker($x, $y));
        }
        elsif ($x >= 0) {
            $s = Math::GMPz::Rmpz_ui_kronecker($x, _any2mpz($y) // (goto &nan));
        }
        else {
            $s = Math::GMPz::Rmpz_si_kronecker($x, _any2mpz($y) // (goto &nan));
        }
    }
    elsif (FAST_MODE and !ref($y)) {
        if ($y >= 0) {
            $s = Math::GMPz::Rmpz_kronecker_ui(_any2mpz($x) // (goto &nan), $y);
        }
        else {
            $s = Math::GMPz::Rmpz_kronecker_si(_any2mpz($x) // (goto &nan), $y);
        }
    }
    else {
        $s = Math::GMPz::Rmpz_kronecker(_any2mpz($x) // (goto &nan), _any2mpz($y) // (goto &nan));
    }

    $s ? (($s == 1) ? ONE : MONE) : ZERO;
}

*Kronecker        = \&kronecker;
*kronecker_symbol = \&kronecker;

sub kronecker_delta {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);
    __eq__($$x, $$y) ? ONE : ZERO;
}

*δ              = \&kronecker_delta;
*KroneckerDelta = \&kronecker_delta;

sub coredisc {
    my ($n) = @_;

    $n = _big2istr($$n) // goto &nan;

    my $sgn = Math::Prime::Util::GMP::cmpint($n, 0);

    if ($sgn == 0) {
        return ZERO;
    }

    my $c = ${(bless \$n)->abs->core};
    $c = __mul__($c, $sgn) if $sgn != 1;
    $c = _any2mpz($c);

    state $r = Math::GMPz::Rmpz_init_nobless();

    my $mod4;
    if (FAST_MODE and Math::GMPz::Rmpz_fits_slong_p($c)) {
        my $v = Math::GMPz::Rmpz_get_si($c);
        $mod4 = $v % 4;
        $mod4 += 4 if $mod4 < 0;
    }
    else {
        $mod4 = Math::GMPz::Rmpz_mod_ui($r, $c, 4);
    }

    if ($mod4 == 1) {
        return bless \$c;
    }

    Math::GMPz::Rmpz_mul_ui($r, $c, 4);
    my $r2 =
        Math::GMPz::Rmpz_fits_slong_p($r)
      ? Math::GMPz::Rmpz_get_si($r)
      : Math::GMPz::Rmpz_init_set($r);
    return bless \$r2;
}

*fundamental_discriminant = \&coredisc;

sub ellcard {
    my ($A, $B, $p) = @_;

    ref($B) eq __PACKAGE__ or _valid(\$B);
    ref($p) eq __PACKAGE__ or _valid(\$p);

    $A = _any2mpz($$A) // goto &nan;
    $B = _any2mpz($$B) // goto &nan;
    $p = _any2mpz($$p) // goto &nan;

    Math::GMPz::Rmpz_sgn($p) > 0 or goto &nan;

    # FAST PATH: PARI/GP Schoof's algorithm
    if ($USE_PARI_GP && Math::GMPz::Rmpz_sizeinbase($p, 2) >= 16) {
        my $A_str = Math::GMPz::Rmpz_get_str($A, 10);
        my $B_str = Math::GMPz::Rmpz_get_str($B, 10);
        my $p_str = Math::GMPz::Rmpz_get_str($p, 10);

        my $code = "iferr(ellcard(ellinit(Mod([$A_str, $B_str], $p_str))), E, \"\")";

        if (my $res = _execute_pari_gp($code)) {
            if ($res =~ /^\d+$/) {
                return _set_int($res);
            }
        }
    }

    # FALLBACK: Legendre symbol summation O(p)
    # card = 1 + p + \sum_{x=0}^{p-1} Legendre(x^3 + ax + b, p)
    my $card = Math::GMPz::Rmpz_init_set($p);
    Math::GMPz::Rmpz_add_ui($card, $card, 1);

    state $x = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($x, 0);

    while (Math::GMPz::Rmpz_cmp($x, $p) < 0) {

        # t = x^3 + ax + b
        Math::GMPz::Rmpz_mul($t, $x, $x);
        Math::GMPz::Rmpz_add($t, $t, $A);
        Math::GMPz::Rmpz_mul($t, $t, $x);
        Math::GMPz::Rmpz_add($t, $t, $B);
        Math::GMPz::Rmpz_mod($t, $t, $p);

        my $kr = Math::GMPz::Rmpz_kronecker($t, $p);

        if ($kr == 1) {
            Math::GMPz::Rmpz_add_ui($card, $card, 1);
        }
        elsif ($kr == -1) {
            Math::GMPz::Rmpz_sub_ui($card, $card, 1);
        }
        Math::GMPz::Rmpz_add_ui($x, $x, 1);
    }

    return bless \$card;
}

*elliptic_curve_cardinality = \&ellcard;

sub hclassno {
    my ($n) = @_;

    # Algorithm from Math::Prime::Util::PP

    $n = _any2mpz($$n) // goto &nan;

    my $sgn = Math::GMPz::Rmpz_sgn($n);

    if ($sgn < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);    # copy
        Math::GMPz::Rmpz_abs($n, $n);
    }
    elsif ($sgn == 0) {
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_si($q, -1, 12);
        return bless \$q;
    }

    if (Math::GMPz::Rmpz_congruent_ui_p($n, 1, 4) or Math::GMPz::Rmpz_congruent_ui_p($n, 2, 4)) {
        return ZERO;
    }

    # PARI/GP for massive discriminants
    if ($USE_PARI_GP and Math::GMPz::Rmpz_sizeinbase($n, 2) >= 24) {
        my $n_str = Math::GMPz::Rmpz_get_str($n, 10);
        my $res   = _execute_pari_gp("qfbhclassno($n_str)");
        if (defined($res) and $res =~ /[0-9]/) {
            my $q = _str2obj($res);
            return bless \$q;
        }
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        state $twelve = _set_int(12);
        return _set_int(Math::Prime::Util::hclassno(Math::GMPz::Rmpz_get_ui($n)))->div($twelve);
    }

    my $square = 0;

    # Pre-allocate all temporary variables to avoid loop allocation overhead
    state $t   = Math::GMPz::Rmpz_init_nobless();
    state $lim = Math::GMPz::Rmpz_init_nobless();
    state $d_z = Math::GMPz::Rmpz_init_nobless();    # For divisor conversion

    my $h  = Math::GMPz::Rmpz_init_set_ui(0);
    my $B  = Math::GMPz::Rmpz_init_set_ui(Math::GMPz::Rmpz_odd_p($n) ? 1 : 0);
    my $B2 = Math::GMPz::Rmpz_init_set($n);

    Math::GMPz::Rmpz_add_ui($B2, $B2, 1);
    Math::GMPz::Rmpz_div_2exp($B2, $B2, 2);

    if (Math::GMPz::Rmpz_sgn($B) == 0) {
        Math::GMPz::Rmpz_sqrt($lim, $B2);

        if (Math::GMPz::Rmpz_perfect_square_p($B2)) {
            $square = 1;
            Math::GMPz::Rmpz_sub_ui($lim, $lim, 1);
        }

        my $count = (
                     HAS_PRIME_UTIL
                     ? Math::Prime::Util::divisor_sum($B2, 0)
                     : Math::Prime::Util::GMP::sigma($B2, 0)
                    ) >> 1;

        Math::GMPz::Rmpz_add_ui($h, $h, $count);

        Math::GMPz::Rmpz_set_ui($B, 2);
        Math::GMPz::Rmpz_set($B2, $n);
        Math::GMPz::Rmpz_add_ui($B2, $B2, 4);
        Math::GMPz::Rmpz_div_2exp($B2, $B2, 2);
    }

    while (1) {

        Math::GMPz::Rmpz_mul_ui($t, $B2, 3);
        Math::GMPz::Rmpz_cmp($t, $n) < 0 or last;

        if (Math::GMPz::Rmpz_divisible_p($B2, $B)) {
            Math::GMPz::Rmpz_add_ui($h, $h, 1);
        }

        Math::GMPz::Rmpz_sqrt($lim, $B2);

        if (Math::GMPz::Rmpz_perfect_square_p($B2)) {
            Math::GMPz::Rmpz_add_ui($h, $h, 1);
            Math::GMPz::Rmpz_sub_ui($lim, $lim, 1);
        }

        my $count = 0;
        foreach my $d (_divisors($B2)) {
            if (FAST_MODE and $d < ULONG_MAX) {
                last     if Math::GMPz::Rmpz_cmp_ui($lim, $d) < 0;
                ++$count if Math::GMPz::Rmpz_cmp_ui($B,   $d) < 0;
            }
            else {
                Math::GMPz::Rmpz_set_str($d_z, "$d", 10);
                last     if Math::GMPz::Rmpz_cmp($d_z, $lim) > 0;
                ++$count if Math::GMPz::Rmpz_cmp($d_z, $B) > 0;
            }
        }

        Math::GMPz::Rmpz_add_ui($h, $h, 2 * $count) if ($count > 0);

        # B = B + 2
        Math::GMPz::Rmpz_add_ui($B, $B, 2);

        # B2 = (B^2 + n) / 4
        Math::GMPz::Rmpz_mul($B2, $B, $B);
        Math::GMPz::Rmpz_add($B2, $B2, $n);
        Math::GMPz::Rmpz_div_2exp($B2, $B2, 2);
    }

    Math::GMPz::Rmpz_mul_ui($t, $B2, 3);

    my $m = ($square ? 2 : 3);

    if ($square or Math::GMPz::Rmpz_cmp($t, $n) == 0) {
        Math::GMPz::Rmpz_mul_ui($h, $h, $m);
        Math::GMPz::Rmpz_add_ui($h, $h, 1);

        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_ui($q, 1, $m);
        Math::GMPq::Rmpq_mul_z($q, $q, $h);
        return bless \$q;
    }

    return bless \$h;
}

sub _sos_k2 {    # OEIS: A004018
    my ($n, $t, $v) = @_;
    return 0 if Math::GMPz::Rmpz_congruent_ui_p($t, 3, 4);

    my $count = Math::GMPz::Rmpz_init_set_ui(4);
    foreach my $pp (_factor_exp($t)) {
        my ($p, $e) = @$pp;
        my $r = (FAST_MODE and $p < ULONG_MAX) ? ($p % 4) : Math::Prime::Util::GMP::modint($p, 4);

        if ($r == 3) {
            return 0 unless $e % 2 == 0;
        }
        if ($r == 1) {
            Math::GMPz::Rmpz_mul_ui($count, $count, $e + 1);
        }
    }
    return $count;
}

sub _sos_k3 {    # OEIS: A005875
    my ($n, $t, $v) = @_;

    ((($v & 1) == 1) || !Math::GMPz::Rmpz_congruent_ui_p($t, 7, 8)) || return 0;

    if (_is_squarefree($n)) {
        if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
            my $count = 0;
            if (Math::GMPz::Rmpz_congruent_ui_p($n, 3, 8)) {
                $count = eval { Math::Prime::Util::GMP::mulint(Math::Prime::Util::hclassno(Math::GMPz::Rmpz_get_ui($n)), 2); };
            }
            else {
                my $tmp_n = $n << 2;
                $count =
                  Math::GMPz::Rmpz_fits_ulong_p($tmp_n)
                  ? eval { Math::Prime::Util::hclassno(Math::GMPz::Rmpz_get_ui($tmp_n)) }
                  : undef;
            }
            return Math::GMPz::Rmpz_init_set_str("$count", 10) if defined($count);
        }

        my $h;
        my $count = Math::GMPz::Rmpz_init();
        if (Math::GMPz::Rmpz_congruent_ui_p($n, 3, 8)) {
            $h = ${(bless \$n)->hclassno->mul(_set_int(24))};
        }
        else {
            $h = ${(bless \$n)->mul(_set_int(4))->hclassno->mul(_set_int(12))};
        }

        $h = _any2mpz($h) if !ref($h);
        (ref($h) eq 'Math::GMPq')
          ? Math::GMPz::Rmpz_set_q($count, $h)
          : Math::GMPz::Rmpz_set($count, $h);

        return $count;
    }

    return undef;
}

sub _sos_k4 {    # OEIS: A000118
    my ($n, $t, $v) = @_;
    my $count = Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::sigma(($v >= 1) ? ($t << 1) : $t), 8);
    return Math::GMPz::Rmpz_init_set_str($count, 10);
}

sub _sos_k6 {    # OEIS: A000141
    my ($n, $t, $v) = @_;

    # A000141: a(n) = 4( Sum_{ d|n, d == 3 mod 4} d^2 - Sum_{ d|n, d == 1 mod 4} d^2 )
    #              + 16( Sum_{ d|n, n/d == 1 mod 4} d^2 - Sum_{ d|n, n/d == 3 mod 4} d^2 )

    # a(n) = 16*A050470(n) - 4*A002173(n).

    # Multiplicative formulas by Jianing Song, where Chi = A101455:
    #   A050470: Multiplicative with a(p^e) = ((p^2)^(e+1) - Chi(p)^(e+1))/(p^2 - Chi(p)).
    #   A002173: Multiplicative with a(p^e) = ((p^2*Chi(p))^(e+1) - 1)/(p^2*Chi(p) - 1).

    my $prod1 = Math::GMPz::Rmpz_init_set_ui(1);
    my $prod2 = Math::GMPz::Rmpz_init_set_ui(1);
    my ($p1, $p2, $u1, $u2) = map { Math::GMPz::Rmpz_init() } 1 .. 4;

    foreach my $pp (_factor_exp($t)) {
        my ($p, $e) = @$pp;

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p1, $p)
          : Math::GMPz::Rmpz_set_str($p1, $p, 10);

        Math::GMPz::Rmpz_pow_ui($u1, $p1, 2 * ($e + 1));
        Math::GMPz::Rmpz_set($u2, $u1);

        my $congr1_4 = Math::GMPz::Rmpz_congruent_ui_p($p1, 1, 4);

        if ($congr1_4 or $e % 2 == 1) {
            Math::GMPz::Rmpz_sub_ui($u1, $u1, 1);
            Math::GMPz::Rmpz_sub_ui($u2, $u2, 1);
        }
        else {
            Math::GMPz::Rmpz_add_ui($u1, $u1, 1);
            Math::GMPz::Rmpz_neg($u2, $u2);
            Math::GMPz::Rmpz_sub_ui($u2, $u2, 1);
        }

        Math::GMPz::Rmpz_mul($p1, $p1, $p1);
        Math::GMPz::Rmpz_set($p2, $p1);

        if ($congr1_4) {
            Math::GMPz::Rmpz_sub_ui($p1, $p1, 1);
        }
        else {
            Math::GMPz::Rmpz_add_ui($p1, $p1, 1);
            Math::GMPz::Rmpz_neg($p2, $p2);
        }
        Math::GMPz::Rmpz_sub_ui($p2, $p2, 1);

        Math::GMPz::Rmpz_divexact($u1, $u1, $p1);
        Math::GMPz::Rmpz_divexact($u2, $u2, $p2);

        Math::GMPz::Rmpz_mul($prod1, $prod1, $u1);
        Math::GMPz::Rmpz_mul($prod2, $prod2, $u2);
    }

    Math::GMPz::Rmpz_mul_2exp($prod1, $prod1, 4 + 2 * $v);
    Math::GMPz::Rmpz_mul_2exp($prod2, $prod2, 2);
    Math::GMPz::Rmpz_sub($prod1, $prod1, $prod2);

    return $prod1;
}

sub _sos_k8 {    # OEIS: A000143
    my ($n, $t, $v) = @_;

    # A138503: a(n) is multiplicative with a(2^e) = -(8^(e+1) - 15) / 7, a(p^e) = ((p^3)^(e+1) - 1) / (p^3 - 1).
    # A138503: Let n = 2^k * m, with m odd, then a(n) = -(8^(k+1) - 15)/7 * sigma_3(m).
    # r_8(n) = 16 * (-1)^n * -A138503(n)

    my $prod = Math::GMPz::Rmpz_init_set_ui(16);

    if ($v > 0) {
        my $s = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_ui_pow_ui($s, 8, $v + 1);
        Math::GMPz::Rmpz_sub_ui($s, $s, 15);
        Math::GMPz::Rmpz_divexact_ui($s, $s, 7);
        Math::GMPz::Rmpz_mul($prod, $prod, $s);
    }

    my $count = Math::Prime::Util::GMP::mulint($prod, Math::Prime::Util::GMP::sigma($t, 3));
    return Math::GMPz::Rmpz_init_set_str($count, 10);
}

sub _sos_k10 {    # OEIS: A000144
    my ($n, $t, $v) = @_;

    # Efficient formula for k = 10, due to Michael Somos:
    # r_10(n) = 4/5 * (A050456(n) + 16*A050468(n) + 8*A030212(n))

    # A050456 is multiplicative with:
    #   a(2^e) = 1
    #   a(p^e) = ((p^4)^(e+1) - 1) / (p^4 - 1)  if p == 1 (mod 4)
    #   a(p^e) = (1 - (-p^4)^(e+1)) / (1 + p^4) if p == 3 (mod 4)

    # A050468 is multiplicative with:
    #   a(2^e) = 16^e
    #   a(p^e) = ((p^4)^(e+1) - 1) / (p^4 - 1)          if p == 1 (mod 4)
    #   a(p^e) = ((p^4)^(e+1) - (-1)^(e+1)) / (p^4 + 1) if p == 3 (mod 4)

    # A030212 is multiplicative with:
    #   a(2^e) = (-4)^e
    #   a(p^e) = p^(2*e) * (1 + (-1)^e)/2             if p == 3 (mod 4)
    #   a(p^e) = a(p) * a(p^(e-1)) - p^4 * a(p^(e-2)) if p == 1 (mod 4)
    # where a(p) = 2 * Re( (x + i*y)^4 ) and p = x^2 + y^2 with even x.

    my @factors = _factor_exp($t);

    my $prod1 = Math::GMPz::Rmpz_init_set_ui(1);
    my $prod2 = Math::GMPz::Rmpz_init_set_ui(1);
    my $prod3 = Math::GMPz::Rmpz_init_set_ui(1);

    state $p  = Math::GMPz::Rmpz_init_nobless();
    state $u1 = Math::GMPz::Rmpz_init_nobless();
    state $u2 = Math::GMPz::Rmpz_init_nobless();

    if (Math::GMPz::Rmpz_congruent_ui_p($t, 3, 4)) {
        Math::GMPz::Rmpz_set_ui($prod3, 0);
    }

    foreach my $factor (Math::GMPz::Rmpz_sgn($prod3) == 0 ? () : @factors) {
        my ($p_str, $e) = @$factor;

        Math::GMPz::Rmpz_set_str($p, $p_str, 10);

        # Case 1: p == 3 (mod 4)
        if (Math::GMPz::Rmpz_congruent_ui_p($p, 3, 4)) {

            if ($e % 2 == 1) {
                Math::GMPz::Rmpz_set_ui($prod3, 0);
                last;    # Short-circuit: the whole product becomes zero
            }

            # a(p^e) = p^(2*e)
            Math::GMPz::Rmpz_pow_ui($u1, $p, 2 * $e);
            Math::GMPz::Rmpz_mul($prod3, $prod3, $u1);
        }

        # Case 2: p == 1 (mod 4)
        else {
            my ($x, $y) = _primitive_sum_of_two_squares($p);

            # Calculate a(p) algebraically: 2 * (x^4 - 6*x^2*y^2 + y^4)
            Math::GMPz::Rmpz_mul($u1, $x, $x);    # x^2
            Math::GMPz::Rmpz_mul($u2, $y, $y);    # y^2

            my $ap  = Math::GMPz::Rmpz_init();
            my $mid = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_mul($mid, $u1, $u2);
            Math::GMPz::Rmpz_mul($ap,  $u1, $u1);      # x^4
            Math::GMPz::Rmpz_mul($u2,  $u2, $u2);      # y^4
            Math::GMPz::Rmpz_mul_ui($mid, $mid, 6);    # 6*x^2*y^2

            Math::GMPz::Rmpz_sub($ap, $ap, $mid);
            Math::GMPz::Rmpz_add($ap, $ap, $u2);
            Math::GMPz::Rmpz_mul_ui($ap, $ap, 2);      # $ap = a(p)

            # Iterative recurrence for a(p^e)
            my $v1 = Math::GMPz::Rmpz_init_set($ap);
            if ($e > 1) {
                Math::GMPz::Rmpz_set_ui($u1, 1);
                Math::GMPz::Rmpz_set_ui($u2, 0);
                Math::GMPz::Rmpz_pow_ui($p, $p, 4);

                for (2 .. $e) {

                    # v2 = ap * v1 - p^4 * v0
                    Math::GMPz::Rmpz_mul($u2, $ap, $v1);
                    Math::GMPz::Rmpz_mul($u1, $p,  $u1);
                    Math::GMPz::Rmpz_sub($u2, $u2, $u1);

                    # Shift variables for next iteration
                    Math::GMPz::Rmpz_set($u1, $v1);
                    Math::GMPz::Rmpz_set($v1, $u2);
                }
            }
            Math::GMPz::Rmpz_mul($prod3, $prod3, $v1);
        }
    }

    # Finally, apply the 2^v component: a(2^v) = (-4)^v
    if ($v >= 1 and Math::GMPz::Rmpz_sgn($prod3)) {
        Math::GMPz::Rmpz_mul_2exp($prod3, $prod3, 2 * $v);
        Math::GMPz::Rmpz_neg($prod3, $prod3) if ($v % 2 == 1);
    }

    foreach my $pp (@factors) {
        my ($p_str, $e) = @$pp;

        (FAST_MODE and $p_str < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $p_str)
          : Math::GMPz::Rmpz_set_str($p, $p_str, 10);

        Math::GMPz::Rmpz_pow_ui($u1, $p, 4 * ($e + 1));
        my $congr1_4 = Math::GMPz::Rmpz_congruent_ui_p($p, 1, 4);
        Math::GMPz::Rmpz_pow_ui($p, $p, 4);

        if ($congr1_4) {
            Math::GMPz::Rmpz_sub_ui($p, $p, 1);
            Math::GMPz::Rmpz_divexact($u1, $u1, $p);
            Math::GMPz::Rmpz_mul($prod1, $prod1, $u1);
            Math::GMPz::Rmpz_mul($prod2, $prod2, $u1);
            next;
        }

        # Here, we have: p == 3 (mod 4)

        Math::GMPz::Rmpz_set($u2, $u1);
        Math::GMPz::Rmpz_add_ui($p, $p, 1);

        if ($e % 2 == 1) {
            Math::GMPz::Rmpz_neg($u1, $u1);
            Math::GMPz::Rmpz_sub_ui($u2, $u2, 1);
        }
        else {
            Math::GMPz::Rmpz_add_ui($u2, $u2, 1);
        }

        Math::GMPz::Rmpz_add_ui($u1, $u1, 1);
        Math::GMPz::Rmpz_divexact($u1, $u1, $p);
        Math::GMPz::Rmpz_divexact($u2, $u2, $p);

        Math::GMPz::Rmpz_mul($prod1, $prod1, $u1);
        Math::GMPz::Rmpz_mul($prod2, $prod2, $u2);
    }

    Math::GMPz::Rmpz_mul_2exp($prod2, $prod2, 4 * $v) if ($v > 0);
    Math::GMPz::Rmpz_mul_2exp($prod2, $prod2, 4);
    Math::GMPz::Rmpz_mul_2exp($prod3, $prod3, 3);
    Math::GMPz::Rmpz_add($prod1, $prod1, $prod2);
    Math::GMPz::Rmpz_add($prod1, $prod1, $prod3);
    Math::GMPz::Rmpz_mul_2exp($prod1, $prod1, 2);
    Math::GMPz::Rmpz_divexact_ui($prod1, $prod1, 5);

    return $prod1;
}

# --- Isolated helper for k=12 prime evaluation ---
sub _compute_b_prime {
    my ($p) = @_;

    $p < ULONG_MAX || die "Too large value!";

    # M = (p - 1) / 2
    my $M = ref($p) ? Math::GMPz::Rmpz_get_ui($p) : $p;
    $M = ($M - 1) >> 1;

    my @S2 = (0) x ($M + 1);
    my @A;

    # Generate the sparse theta series A(q) up to degree M
    my $k = 0;
    while (1) {
        my $Tk = ($k * ($k + 1)) >> 1;
        last if $Tk > $M;
        my $val = 2 * $k + 1;
        $val = -$val if $k % 2 != 0;
        push @A, [$Tk, $val];
        $k++;
    }

    # Compute A(q)^2 in O(M) time due to sparsity
    for my $i (0 .. $#A) {
        my ($T1, $v1) = @{$A[$i]};
        for my $j (0 .. $#A) {
            my ($T2, $v2) = @{$A[$j]};
            my $sum_T = $T1 + $T2;
            last if $sum_T > $M;
            $S2[$sum_T] += $v1 * $v2;
        }
    }

    # Find the coefficient of q^M in (A(q)^2)^2
    my $ans    = Math::GMPz::Rmpz_init_set_ui(0);
    my $term   = Math::GMPz::Rmpz_init();
    my $z_s2_i = Math::GMPz::Rmpz_init();
    my $z_s2_j = Math::GMPz::Rmpz_init();

    for my $i (0 .. $M) {
        my $j = $M - $i;
        if ($S2[$i] && $S2[$j]) {
            Math::GMPz::Rmpz_set_str($z_s2_i, "$S2[$i]", 10);
            Math::GMPz::Rmpz_set_str($z_s2_j, "$S2[$j]", 10);
            Math::GMPz::Rmpz_mul($term, $z_s2_i, $z_s2_j);
            Math::GMPz::Rmpz_add($ans, $ans, $term);
        }
    }

    return $ans;
}

sub _sos_k12 {
    my ($n) = @_;

    # r_12(n) = A000145(n)
    #         = 8*σ_5​(n) - 512*σ_5​(n/4) + 16*b(n)
    #         = 8*sigma_5(n) - f(n) + g(n), where f(n) = 512*sigma_5(n/4) if 4|n, else 0, and g(n) = 16*A000735((n-1)/2) if n is odd, else 0
    #
    # where b(n) is strongly multiplicative with:
    #   b(p^e) = b(p) * b(p^(e−1)) - p^5 * b(p^(e-2)), for odd prime p.
    #   b(2^e) = 1
    #
    # b(p) is the coefficient of q^((p−1)/2) in A(q)^4, where:
    #    A(q) = Sum_{k>=0} ​(−1)^k * (2*k+1) * q^(k*(k+1)/2).

    # Part 1: Compute 8 * sigma_5(n)
    my $A = Math::Prime::Util::GMP::sigma($n, 5);
    $A = Math::Prime::Util::GMP::mulint($A, 8);
    my $termA = Math::GMPz::Rmpz_init_set_str("$A", 10);

    # Part 2: Compute 512 * sigma_5(n/4) if 4|n
    my $termB = Math::GMPz::Rmpz_init_set_ui(0);
    if (Math::GMPz::Rmpz_divisible_2exp_p($n, 2)) {
        my $n_over_4 = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_fdiv_q_2exp($n_over_4, $n, 2);

        my $B = Math::Prime::Util::GMP::sigma($n_over_4, 5);
        $B = Math::Prime::Util::GMP::mulint($B, 512);
        Math::GMPz::Rmpz_set_str($termB, "$B", 10);
    }

    my $ans = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub($ans, $termA, $termB);

    Math::GMPz::Rmpz_odd_p($n) || return $ans;

    # Part 3: Apply the sequence logic + 16*b(n) for odd numbers
    my $b_n = Math::GMPz::Rmpz_init_set_ui(1);

    state %b_prime_cache;
    undef %b_prime_cache if scalar(keys(%b_prime_cache)) > 1e5;

    foreach my $pp (_factor_exp($n)) {
        my ($p, $e) = @$pp;

        my $b_p = ($b_prime_cache{$p} //= _compute_b_prime($p));

        if ($e == 1) {
            Math::GMPz::Rmpz_mul($b_n, $b_n, $b_p);
            next;
        }

        # Handle Multiplicative Prime Powers: b(p^e) = b(p)*b(p^{e-1}) - p^5*b(p^{e-2})
        my $b_prev = Math::GMPz::Rmpz_init_set_ui(1);
        my $b_curr = Math::GMPz::Rmpz_init_set($b_p);

        my $p_pow5 = Math::GMPz::Rmpz_init_set_str($p, 10);
        Math::GMPz::Rmpz_pow_ui($p_pow5, $p_pow5, 5);

        my $b_next = Math::GMPz::Rmpz_init();
        my $term   = Math::GMPz::Rmpz_init();

        for my $k (2 .. $e) {
            Math::GMPz::Rmpz_mul($b_next, $b_p,    $b_curr);
            Math::GMPz::Rmpz_mul($term,   $p_pow5, $b_prev);
            Math::GMPz::Rmpz_sub($b_next, $b_next, $term);

            Math::GMPz::Rmpz_set($b_prev, $b_curr);
            Math::GMPz::Rmpz_set($b_curr, $b_next);
        }
        Math::GMPz::Rmpz_mul($b_n, $b_n, $b_curr);
    }

    Math::GMPz::Rmpz_mul_ui($b_n, $b_n, 16);
    Math::GMPz::Rmpz_add($ans, $ans, $b_n);

    return $ans;
}

sub _sos_k24 {
    my ($n) = @_;

    # r_24(n) = A000156(n)
    #         = (2^4*(sigma_{11}(n)- 2*sigma_{11}(n/2) + 2^{12}*sigma_{11}(n/4)) - 2^7*259*(-1)^n*tau(n) - 2^16*tau(n/2))/691

    # Sigma_{11}(x) terms (A013959)
    # They evaluate to 0 if the argument x is not an integer
    my $sigma_n   = Math::Prime::Util::GMP::sigma($n, 11);
    my $sigma_n_2 = Math::GMPz::Rmpz_divisible_ui_p($n, 2) ? Math::Prime::Util::GMP::sigma($n >> 1, 11) : 0;
    my $sigma_n_4 = Math::GMPz::Rmpz_divisible_ui_p($n, 4) ? Math::Prime::Util::GMP::sigma($n >> 2, 11) : 0;

    # Ramanujan's tau(x) terms (A000594)
    # They evaluate to 0 if the argument x is not an integer
    my $tau_n   = ${_set_int($n)->ramanujan_tau};
    my $tau_n_2 = Math::GMPz::Rmpz_even_p($n) ? ${_set_int($n >> 1)->ramanujan_tau} : 0;

    # Determine the parity sign for (-1)^n
    my $sign = Math::GMPz::Rmpz_even_p($n) ? 1 : -1;

    # Term 1: 2^4 * (sigma_{11}(n) - 2*sigma_{11}(n/2) + 2^{12}*sigma_{11}(n/4))
    my $t1    = Math::Prime::Util::GMP::mulint(2,    $sigma_n_2);
    my $t2    = Math::Prime::Util::GMP::mulint(4096, $sigma_n_4);
    my $t3    = Math::Prime::Util::GMP::subint($sigma_n, $t1);
    my $t4    = Math::Prime::Util::GMP::addint($t3, $t2);
    my $term1 = Math::Prime::Util::GMP::mulint(16, $t4);

    # Term 2: 2^7 * 259 * (-1)^n * tau(n)
    my $term2 = Math::Prime::Util::GMP::vecprod(33152, $sign, $tau_n);

    # Term 3: 2^16 * tau(n/2)
    my $term3 = Math::Prime::Util::GMP::mulint(65536, $tau_n_2);

    # Final Assembly
    my $t5  = Math::Prime::Util::GMP::subint($term1, $term2);
    my $t6  = Math::Prime::Util::GMP::subint($t5,    $term3);
    my $a_n = Math::Prime::Util::GMP::divint($t6, 691);

    Math::GMPz::Rmpz_init_set_str($a_n, 10);
}

# Core computation and caching engine
sub _compute_sos_count {
    my ($n, $k) = @_;

    my $sgn = Math::GMPz::Rmpz_sgn($n);

    $sgn < 0  and return 0;
    $sgn == 0 and return 1;

    return 0 if ($k <= 0);

    if ($k == 1) {
        return 2 if Math::GMPz::Rmpz_perfect_square_p($n);
        return 0;
    }

    # r_3(4*n) = r_3(n)
    if ($k == 3 and Math::GMPz::Rmpz_divisible_2exp_p($n, 2)) {
        $n = Math::GMPz::Rmpz_init_set($n);    # copy
        Math::GMPz::Rmpz_div_2exp($n, $n, 2);
    }

    # Dispatch to optimized hardcoded formulas for known k
    my %optimized_handlers = (
                              2  => \&_sos_k2,
                              3  => \&_sos_k3,
                              4  => \&_sos_k4,
                              6  => \&_sos_k6,
                              8  => \&_sos_k8,
                              10 => \&_sos_k10,
                              12 => \&_sos_k12,
                              24 => \&_sos_k24,
                             );

    if (exists $optimized_handlers{$k}) {

        my $t   = Math::GMPz::Rmpz_init_set($n);
        my $v   = Math::GMPz::Rmpz_remove($t, $t, $TWO);
        my $res = $optimized_handlers{$k}->($n, $t, $v);

        if (defined($res)) {
            return $res;
        }
        else {
            # Fallback otherwise
        }
    }

    # General Fallback (Arbitrary k) with bounds-checked cache
    state %cache;
    undef %cache if scalar(keys(%cache)) > 1e6;

    my $key = "$n $k";
    return $cache{$key} if exists $cache{$key};

    my $count = Math::GMPz::Rmpz_init_set_ui(0);
    my $tmp   = Math::GMPz::Rmpz_init_set($n);

    foreach my $v (0 .. Math::Prime::Util::GMP::sqrtint($n)) {
        my $u = _compute_sos_count($tmp, $k - 1);

        ref($u)
          ? Math::GMPz::Rmpz_addmul_ui($count, $u, (($v == 0) ? 1 : 2))
          : Math::GMPz::Rmpz_add_ui($count, $count, $u * (($v == 0) ? 1 : 2));

        Math::GMPz::Rmpz_sub_ui($tmp, $tmp, 2 * $v + 1);
    }

    return $cache{$key} = $count;
}

# Main entry point
sub sum_of_squares_count {
    my ($n, $k) = @_;

    # 1. Validation and Setup
    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
    }
    else {
        $k = 2;
    }

    $n = _any2mpz($$n) // goto &nan;

    my $sgn = Math::GMPz::Rmpz_sgn($n);

    $sgn < 0  and return ZERO;
    $sgn == 0 and return ONE;

    if ($k <= 0) {
        return ZERO;
    }

    if ($k == 1) {
        return TWO if Math::GMPz::Rmpz_perfect_square_p($n);
        return ZERO;
    }

    # 2. Delegate to the recursive computation engine
    my $result_val = _compute_sos_count($n, $k);
    return _set_int($result_val);
}

*squares_r = \&sum_of_squares_count;

sub is_ntf {
    my ($x, $y) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        $x > 1 or return $FALSE;

        my $res;

        ref($y) eq __PACKAGE__ or _valid(\$y);
        $y = $$y;

        if (!ref($y)) {
            $res = (($x < $y) and ($y % $x == 0));
        }
        elsif (ref($y) eq 'Math::GMPz') {
            $res = (Math::GMPz::Rmpz_cmp_ui($y, $x) > 0 and Math::GMPz::Rmpz_divisible_ui_p($y, $x));
        }

        if (defined($res)) {
            return ($res ? $TRUE : $FALSE);
        }
    }

    $x = _sanitize_mpz($x) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($x, 1) > 0
      or return $FALSE;

    ref($y) eq __PACKAGE__ or _valid(\$y);
    $y = _sanitize_mpz($$y) // return $FALSE;

    (Math::GMPz::Rmpz_cmp($x, $y) < 0 and Math::GMPz::Rmpz_divisible_p($y, $x)) ? $TRUE : $FALSE;
}

*is_nontrivial_factor = \&is_ntf;

sub is_coprime {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x)) {
        my $r = (!ref($y)) ? ((HAS_PRIME_UTIL ? Math::Prime::Util::gcd($x, $y) : Math::Prime::Util::GMP::gcd($x, $y)) == 1) : do {
            $y = _sanitize_mpz($y) // return $FALSE;
            Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $y, CORE::abs($x)) == 1;
        };
        return ($r ? $TRUE : $FALSE);
    }
    elsif (!ref($y)) {
        $x = _sanitize_mpz($x) // return $FALSE;
        return ((Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $x, CORE::abs($y)) == 1) ? $TRUE : $FALSE);
    }

    $x = _sanitize_mpz($x) // return $FALSE;
    $y = _sanitize_mpz($y) // return $FALSE;

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($t, $x, $y);

    (Math::GMPz::Rmpz_cmp_ui($t, 1) == 0) ? $TRUE : $FALSE;
}

sub gcd {
    my (@vals) = @_;
    _valid(\(@vals));

    @vals || return ZERO;    # By convention, gcd of an empty set is 0.

    if (@vals == 1) {
        my $n = $vals[0];
        __is_int__($$n) || goto &nan;
        return $n->iabs;
    }

    if (@vals > 2) {
        state $r = Math::GMPz::Rmpz_init_nobless();

        # Initialize accumulator with first element
        my $first = shift(@vals);
        Math::GMPz::Rmpz_set($r, _any2mpz($$first) // goto &nan);

        foreach my $val (@vals) {
            my $v = $$val;

            # Fast-track native integers to avoid GMP object allocation
            if (FAST_MODE && !ref($v)) {
                Math::GMPz::Rmpz_gcd_ui($r, $r, CORE::abs($v));
            }
            else {
                my $z = _any2mpz($v) // goto &nan;
                Math::GMPz::Rmpz_gcd($r, $r, $z);
            }

            last if Math::GMPz::Rmpz_cmp_ui($r, 1) == 0;
        }

        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);

        return bless \$r2;
    }

    my ($x, $y) = @vals;

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x)) {
        if (!ref($y)) {
            return bless(\(my $g = (HAS_PRIME_UTIL ? Math::Prime::Util::gcd($x, $y) : Math::Prime::Util::GMP::gcd($x, $y))));
        }
        $y = _sanitize_mpz($y) // goto &nan;
        return bless(\(my $g = Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $y, CORE::abs($x))));
    }
    elsif (FAST_MODE and !ref($y)) {
        $x = _sanitize_mpz($x) // goto &nan;
        return bless(\(my $g = Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $x, CORE::abs($y))));
    }

    $x = _sanitize_mpz($x) // goto &nan;
    $y = _sanitize_mpz($y) // goto &nan;

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub gcud {    # greatest common unitary divisor (OEIS: A165430)
    my (@vals) = @_;
    _valid(\(@vals));

    @vals || return ZERO;    # By convention, gcd of an empty set is 0.

    if (@vals == 1) {
        my $n = $vals[0];
        __is_int__($$n) || goto &nan;
        return $n->iabs;
    }

    my @terms = map { _any2mpz($$_) // goto &nan } @vals;
    my $g     = Math::GMPz::Rmpz_init_set($terms[0]);

    foreach my $i (1 .. $#terms) {
        Math::GMPz::Rmpz_gcd($g, $g, $terms[$i]);
        if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
            return bless \$g;
        }
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    foreach my $n (@terms) {
        next if (Math::GMPz::Rmpz_sgn($n) == 0);
        while (1) {
            Math::GMPz::Rmpz_divexact($t, $n, $g);
            Math::GMPz::Rmpz_gcd($t, $t, $g);
            last if (Math::GMPz::Rmpz_cmp_ui($t, 1) == 0);
            Math::GMPz::Rmpz_divexact($g, $g, $t);
        }
        last if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0);
    }

    $g = Math::GMPz::Rmpz_get_ui($g) if Math::GMPz::Rmpz_fits_ulong_p($g);
    bless \$g;
}

sub gcdext {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // return (nan(), nan());
    $k = _any2mpz($$k) // return (nan(), nan());

    my $g = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init();
    my $v = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_gcdext($g, $u, $v, $n, $k);

    ((bless \$u), (bless \$v), (bless \$g));
}

sub __lcm__ {
    my ($n, $k) = @_;

    if (!ref($n)) {
        ($n, $k) = ($k, $n);
    }

    if (!ref($n)) {
        if (HAS_PRIME_UTIL and $n * $k < ULONG_MAX) {
            return Math::Prime::Util::lcm($n, $k);
        }
        my $t = Math::Prime::Util::GMP::lcm($n, $k);
        $t < ULONG_MAX and return $t;
        return Math::GMPz::Rmpz_init_set_str($t, 10);
    }
    my $r = Math::GMPz::Rmpz_init();
    ref($k)
      ? Math::GMPz::Rmpz_lcm($r, $n, $k)
      : Math::GMPz::Rmpz_lcm_ui($r, $n, CORE::abs($k));
    $r;
}

sub lcm {
    my (@vals) = @_;
    _valid(\(@vals));

    @vals or return ONE;    # By convention, lcm of an empty set is 1.

    if (@vals == 1) {
        my $n = $vals[0];
        __is_int__($$n) || goto &nan;
        return $n->iabs;
    }

    if (@vals > 2) {
        my @terms = map { ref($_) ? (_any2mpz($_) // goto &nan) : $_ } map { $$_ } @vals;
        my $r     = _binsplit(\@terms, \&__lcm__);
        $r = Math::GMPz::Rmpz_get_ui($r) if (ref($r) and Math::GMPz::Rmpz_fits_ulong_p($r));
        return bless \$r;
    }

    my ($x, $y) = @vals;

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y) and CORE::abs($x) * CORE::abs($y) < ULONG_MAX) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::lcm($x, $y)
          : Math::Prime::Util::GMP::lcm($x, $y);
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_lcm($r, $x, $y);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub consecutive_integer_lcm {
    my ($n) = @_;
    _set_int(Math::Prime::Util::GMP::consecutive_integer_lcm(_big2uistr($$n) // goto &nan));
}

*consecutive_lcm = \&consecutive_integer_lcm;

sub num2perm {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    my @perm = map { bless \$_ } Math::Prime::Util::GMP::numtoperm(_big2uistr($$n) // (return undef), _big2uistr($$k) // (return undef));
    _array(\@perm);
}

sub valuation {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {
        $y = CORE::abs($y) if ($y < 0);
        $y <= 1 and return ZERO;
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::valuation($x, $y)
          : Math::Prime::Util::GMP::valuation($x, $y);
        defined($r) or return ZERO;
        return bless \$r;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;

    Math::GMPz::Rmpz_cmpabs_ui($y, 1) <= 0 and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $r = Math::GMPz::Rmpz_remove($t, $x, $y);
    bless \$r;
}

*multiplicity = \&valuation;

sub remove {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($x) and !ref($y)) {

        CORE::abs($y) <= 1 and return bless \$x;

        my $v =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::valuation(CORE::abs($x), CORE::abs($y))
          : Math::Prime::Util::GMP::valuation(CORE::abs($x), CORE::abs($y));

        if ($v == 0) {
            return bless \$x;
        }

        if ($v == 1) {
            my $q =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divint($x, $y)
              : Math::Prime::Util::GMP::divint($x, $y);
            return bless \$q;
        }

        my $q =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($x, Math::Prime::Util::powint($y, $v))
          : Math::Prime::Util::GMP::divint($x, Math::Prime::Util::GMP::powint($y, $v));
        return bless \$q;
    }

    $x = _any2mpz($x) // goto &nan;
    $y = _any2mpz($y) // goto &nan;

    Math::GMPz::Rmpz_cmpabs_ui($y, 1) <= 0 and return $_[0];

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_remove($r, $x, $y);
    bless \$r;
}

*remdiv = \&remove;

sub make_coprime {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) == 0) {
        return _set_int($n);
    }

    my $r = Math::GMPz::Rmpz_init_set($n);
    my $g = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_gcd($g, $r, $k);

    while (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
        Math::GMPz::Rmpz_remove($r, $r, $g);
        Math::GMPz::Rmpz_gcd($g, $r, $g);
    }

    bless \$r;
}

sub useed {
    my ($n) = @_;

    state $_x = require Digest::SHA;

    my $z = _any2mpz($$n) // die "[ERROR] Number.useed(): invalid seed value <<$n>> (expected an integer)";

    my $hex = Math::GMPz::Rmpz_get_str($z, 16);
    $hex = substr($hex, 1) if (substr($hex, 0, 1) eq '-');

    my $bin  = CORE::pack('H*', $hex);
    my $seed = Digest::SHA::sha512($bin);

    while (CORE::length($seed) < 1024) {
        $seed = Digest::SHA::sha512($seed) . CORE::reverse($seed);
    }

    Math::Prime::Util::GMP::seed_csprng(1024, $seed);
    return $TRUE;
}

*iseed = \&useed;

sub urandomm {
    my ($n, $m) = @_;

    if (defined($m)) {
        ref($m) eq __PACKAGE__ or _valid(\$m);

        $n = $$n;
        $m = $$m;

        $n = (ref($n) ? _any2mpz($n) : $n) // return ZERO;
        $m = (ref($m) ? _any2mpz($m) : $m) // return ZERO;

        if (__cmp__($n, $m) > 0) {
            ($n, $m) = ($m, $n);
        }

        if (ref($n)) {
            $n = _big2uistr($n) // 0;
        }
        else {
            $n >= 0 or do { $n = 0 };
        }

        if (ref($m)) {
            $m = _big2uistr($m) // 0;
        }
        else {
            $m >= 0 or do { $m = 0 };
        }

        return _set_int(Math::Prime::Util::GMP::urandomr($n, $m) || return ZERO);
    }

    _set_int(Math::Prime::Util::GMP::urandomm(_big2uistr($$n) || return ZERO) || return ZERO);
}

*urand = \&urandomm;

sub irand {
    my ($n, $m) = @_;

    if (defined($m)) {
        ref($m) eq __PACKAGE__ or _valid(\$m);
    }
    else {
        ($n, $m) = (ZERO, $n);
    }

    $n = $$n;
    $m = $$m;

    $n = (ref($n) ? _any2mpz($n) : $n) // goto &nan;
    $m = (ref($m) ? _any2mpz($m) : $m) // goto &nan;

    if (ref($n) or ref($m)) {
        $n = _any2mpz($n) if !ref($n);
        $m = _any2mpz($m) if !ref($m);

        if (Math::GMPz::Rmpz_cmp($n, $m) > 0) {
            ($n, $m) = ($m, $n);
        }

        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_sub($r, $m, $n);
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
        Math::GMPz::Rmpz_set_str($r, Math::Prime::Util::GMP::urandomm(Math::GMPz::Rmpz_get_str($r, 10)) // "0", 10);
        Math::GMPz::Rmpz_add($r, $r, $n);
        return bless \$r;
    }

    if ($n > $m) {
        ($n, $m) = ($m, $n);
    }

    my $r =
      (FAST_MODE and $n == 0 and $m < ULONG_MAX) ? ($m + 1)
      : (
         HAS_PRIME_UTIL ? Math::Prime::Util::addint(Math::Prime::Util::subint($m, $n), 1)
         : Math::Prime::Util::GMP::addint(Math::Prime::Util::GMP::subint($m, $n), 1)
        );

    $r = Math::Prime::Util::GMP::urandomm($r) // 0;

    if ($n != 0) {
        $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::addint($r, $n)
          : Math::Prime::Util::GMP::addint($r, $n);
    }

    _set_int($r);
}

sub random_prime {
    my ($from, $to) = @_;

    $from = _big2uistr($$from) // (goto &nan);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $to = _big2uistr($$to) // (goto &nan);
        return _set_int(Math::Prime::Util::GMP::random_prime($from, $to) // goto &nan);
    }

    _set_int(Math::Prime::Util::GMP::random_prime(2, $from) // goto &nan);
}

sub random_bytes {
    my ($n) = @_;
    _array([map { bless(\(my $o = ord($_))) } split(//, Math::Prime::Util::GMP::random_bytes(_big2uistr($$n) // (return undef)))]);
}

sub random_string {
    my ($n) = @_;
    Sidef::Types::String::String->new(Math::Prime::Util::GMP::random_bytes(_big2uistr($$n) // (return undef)));
}

sub random_nbit_prime {
    my ($n) = @_;
    $n = _any2ui($$n) || goto &nan;
    _set_int(Math::Prime::Util::GMP::random_nbit_prime($n) || goto &nan);
}

sub random_nbit_safe_prime {
    my ($n) = @_;
    $n = _any2ui($$n) || goto &nan;
    _set_int(Math::Prime::Util::GMP::random_safe_prime($n) || goto &nan);
}

sub random_nbit_strong_prime {
    my ($n) = @_;
    $n = _any2ui($$n) || goto &nan;
    $n < 128 && goto &random_nbit_prime;
    _set_int(Math::Prime::Util::GMP::random_strong_prime($n) || goto &nan);
}

*random_strong_nbit_prime = \&random_nbit_strong_prime;

sub random_nbit_maurer_prime {
    my ($n) = @_;
    $n = _any2ui($$n) || goto &nan;
    _set_int(Math::Prime::Util::GMP::random_maurer_prime($n) || goto &nan);
}

*random_maurer_nbit_prime = \&random_nbit_maurer_prime;

sub random_ndigit_prime {
    my ($n) = @_;
    $n = _any2ui($$n) || goto &nan;
    _set_int(Math::Prime::Util::GMP::random_ndigit_prime($n) || goto &nan);
}

sub is_semiprime {
    my ($x) = @_;

    $x = $$x;

    if (FAST_MODE and !ref($x)) {
        my $r = (
                 $x >= 4
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_semiprime($x)
                        : Math::Prime::Util::GMP::is_semiprime($x)
                       )
                );
        return ($r ? $TRUE : $FALSE);
    }

    __is_int__($x) || return $FALSE;
    $x = _any2mpz($x) // return $FALSE;

    # When n is large enough, is_almost_prime(n,2) is faster
    if (Math::GMPz::Rmpz_sizeinbase($x, 10) > FACTORDB_MIN) {
        return _set_int($x)->is_almost_prime(TWO);
    }

    Math::GMPz::Rmpz_sgn($x) > 0
      or return $FALSE;

    my $result = Math::Prime::Util::GMP::is_semiprime(Math::GMPz::Rmpz_get_str($x, 10));

    $result ? $TRUE : $FALSE;
}

sub is_squarefree_semiprime {
    my ($n) = @_;
    if ($n->is_square) {
        return $FALSE;
    }
    $n->is_semiprime;
}

sub _semiprime_count {
    my ($n) = @_;

    state $pi2_table = {

        # Number of semiprimes <= 10^n
        # OEIS: https://oeis.org/A036352
        "10"                       => "4",
        "100"                      => "34",
        "1000"                     => "299",
        "10000"                    => "2625",
        "100000"                   => "23378",
        "1000000"                  => "210035",
        "10000000"                 => "1904324",
        "100000000"                => "17427258",
        "1000000000"               => "160788536",
        "10000000000"              => "1493776443",
        "100000000000"             => "13959990342",
        "1000000000000"            => "131126017178",
        "10000000000000"           => "1237088048653",
        "100000000000000"          => "11715902308080",
        "1000000000000000"         => "111329817298881",
        "10000000000000000"        => "1061057292827269",
        "100000000000000000"       => "10139482913717352",
        "1000000000000000000"      => "97123037685177087",
        "10000000000000000000"     => "932300026230174178",
        "100000000000000000000"    => "8966605849641219022",
        "1000000000000000000000"   => "86389956293761485464",
        "10000000000000000000000"  => "833671466551239927908",
        "100000000000000000000000" => "8056846659984852885191",

        # Number of semiprimes <= 2^n
        # OEIS: https://oeis.org/A125527
        "4294967296"          => "658662065",
        "8589934592"          => "1289149627",
        "17179869184"         => "2524532330",
        "34359738368"         => "4946320619",
        "68719476736"         => "9696090315",
        "137438953472"        => "19015826478",
        "274877906944"        => "37310368709",
        "549755813888"        => "73237005168",
        "1099511627776"       => "143817246008",
        "2199023255552"       => "282528883551",
        "4398046511104"       => "555237939294",
        "8796093022208"       => "1091574618496",
        "17592186044416"      => "2146738817329",
        "35184372088832"      => "4223287872953",
        "70368744177664"      => "8311168557633",
        "140737488355328"     => "16360940729894",
        "281474976710656"     => "32216929163102",
        "562949953421312"     => "63457786440404",
        "1125899906842624"    => "125027663135664",
        "2251799813685248"    => "246401562625117",
        "4503599627370496"    => "485727708027940",
        "9007199254740992"    => "957746412122119",
        "18014398509481984"   => "1888916491053636",
        "36028797018963968"   => "3726284941841117",
        "72057594037927936"   => "7352535573376770",
        "144115188075855872"  => "14510848832845041",
        "288230376151711744"  => "28644424519528512",
        "576460752303423488"  => "56555665405788147",
        "1152921504606846976" => "111685842309849897",
        "2305843009213693952" => "220598984393061174",
        "4611686018427387904" => "435802554444335050",
        "9223372036854775808" => "861101852602340550",
                       };

    if (defined(my $value = $pi2_table->{$n})) {
        return $value;
    }

    if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
        return Math::Prime::Util::semiprime_count($n);
    }

    my $t = 0;
    my $s = Math::Prime::Util::GMP::sqrtint($n);

    my $count = 0;
    foreach my $p (@{_primes(2, $s)}) {
        if (INTSIZE <= 32) {
            $count = Math::Prime::Util::GMP::addint($count, Math::Prime::Util::GMP::subint(_prime_count(Math::Prime::Util::GMP::divint($n, $p)), ++$t - 1));
        }
        else {
            $count += _prime_count(CORE::int($n / $p)) - ++$t + 1;
        }
    }

    return $count;
}

sub semiprime_count {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->semiprime_count->sub($from->dec->semiprime_count);
    }

    my $n = _big2uistr($$from) // return ZERO;
    _set_int(_semiprime_count($n));
}

*semiprimes_count = \&semiprime_count;

sub semiprime_sum {
    (TWO)->almost_prime_sum(@_);
}

*semiprimes_sum = \&semiprime_sum;

sub nth_semiprime {
    my ($n) = @_;

    $n = $$n;
    $n = (ref($n) ? _any2mpz($n) : $n) // goto &nan;
    $n < 0 and goto &nan;

    return ONE         if ($n == 0);    # not semiprime, but...
    return _set_int(4) if ($n == 1);

    my ($min, $max);

    if ($n < 3e3) {
        $min = 4;
        $max = 11465;
    }
    else {
        my $k = ref($n) ? _big2uistr($n) : $n;

        # n-th semiprime is ~ n * log(n) / log(log(n))
        $max = CORE::int($k * CORE::log($k) / CORE::log(CORE::log($k)));
        $min = CORE::int(0.965 * $max);
    }

    if (HAS_PRIME_UTIL and $max < ULONG_MAX) {
        return _set_int(Math::Prime::Util::nth_semiprime($n));
    }

    require Memoize;
    Memoize::memoize('_prime_count');

    my $k     = 0;
    my $count = 0;

    while (1) {
        $k =
          (HAS_PRIME_UTIL and $max < ULONG_MAX)
          ? Math::Prime::Util::divint(Math::Prime::Util::addint($min, $max), 2)
          : Math::Prime::Util::GMP::divint(Math::Prime::Util::GMP::addint($min, $max), 2);

        $count = _semiprime_count($k);

        if ($k > (ULONG_MAX >> 1)) {
            $k = _str2obj("$k");
        }

        if ($count > (ULONG_MAX >> 1)) {
            $count = _str2obj("$count");
        }

        if (CORE::abs($count - $n) <= CORE::sqrt($k)) {
            last;
        }

        my $cmp = ($count <=> $n);

        if ($cmp > 0) {
            $max = $k - 1;
        }
        elsif ($cmp < 0) {
            $min = $k + 1;
        }
        else {
            last;
        }
    }

    Memoize::unmemoize('_prime_count');

    until (HAS_PRIME_UTIL ? Math::Prime::Util::is_semiprime($k) : Math::Prime::Util::GMP::is_semiprime($k)) {
        --$k;
    }

    my $cmp = ($n <=> $count);
    while ($count != $n) {
        do { $k += $cmp } until (HAS_PRIME_UTIL ? Math::Prime::Util::is_semiprime($k) : Math::Prime::Util::GMP::is_semiprime($k));
        $count += $cmp;
    }

    _set_int($k);
}

*semiprime = \&nth_semiprime;

sub next_semiprime {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 4) < 0 and return _set_int(4);

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_semiprime($n) : Math::Prime::Util::GMP::is_semiprime($n)) {
            ++$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    until (Math::Prime::Util::GMP::is_semiprime(Math::GMPz::Rmpz_get_str($r, 10))) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    bless \$r;
}

sub prev_semiprime {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 4) <= 0 and goto &nan;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) - 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_semiprime($n) : Math::Prime::Util::GMP::is_semiprime($n)) {
            --$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    until (Math::Prime::Util::GMP::is_semiprime(Math::GMPz::Rmpz_get_str($r, 10))) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    bless \$r;
}

sub next_squarefree_semiprime {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 6) < 0 and return _set_int(6);

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        until (
               HAS_PRIME_UTIL
               ? (Math::Prime::Util::is_semiprime($n) && !Math::Prime::Util::is_square($n))
               : (Math::Prime::Util::GMP::is_semiprime($n) && !Math::Prime::Util::GMP::is_square($n))
          ) {
            ++$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    until (
        do {
            my $str = Math::GMPz::Rmpz_get_str($r, 10);
            Math::Prime::Util::GMP::is_semiprime($str) && !Math::Prime::Util::GMP::is_square($str);
        }
      ) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    bless \$r;
}

sub prev_squarefree_semiprime {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 6) <= 0 and goto &nan;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) - 1;
        until (
               HAS_PRIME_UTIL
               ? (Math::Prime::Util::is_semiprime($n) && !Math::Prime::Util::is_square($n))
               : (Math::Prime::Util::GMP::is_semiprime($n) && !Math::Prime::Util::GMP::is_square($n))
          ) {
            --$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    until (
        do {
            my $str = Math::GMPz::Rmpz_get_str($r, 10);
            Math::Prime::Util::GMP::is_semiprime($str) && !Math::Prime::Util::GMP::is_square($str);
        }
      ) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    bless \$r;
}

sub _primality_pretest {
    my ($n) = @_;

    if (FAST_MODE and !ref($n)) {
        $n > 1 or return;

        if ($n % 2 == 0) {
            return ($n == 2);
        }

        $n > 101 or return 1;

        if (HAS_PRIME_UTIL and INTSIZE >= 64) {
            Math::Prime::Util::gcd($n, 16294579238595022365) == 1 or return 0;
            Math::Prime::Util::gcd($n, 7145393598349078859) == 1  or return 0;
        }
        else {
            Math::Prime::Util::GMP::gcd($n, "16294579238595022365") eq '1' or return 0;
            Math::Prime::Util::GMP::gcd($n, "7145393598349078859") eq '1'  or return 0;
        }

        return 1;
    }

    $n = _sanitize_mpz($n) // return;

    # Must be greater than 1 (first check)
    (Math::GMPz::Rmpz_cmp_ui($n, 1) > 0) || return;

    # Check for divisibility by 2
    if (Math::GMPz::Rmpz_even_p($n)) {
        return (Math::GMPz::Rmpz_cmp_ui($n, 2) == 0);
    }

    # Return early if n is too small
    Math::GMPz::Rmpz_cmp_ui($n, 101) > 0 or return 1;

    # Check for very small factors
    if (INTSIZE >= 64) {
        Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $n, 16294579238595022365) == 1 or return 0;
        Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $n, 7145393598349078859) == 1  or return 0;
    }
    else {
        Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $n, 3234846615) == 1 or return 0;
    }

    # Native integer -- return early
    Math::GMPz::Rmpz_fits_ulong_p($n) && return 1;

    # Size of n in base-10
    my $size = Math::GMPz::Rmpz_sizeinbase($n, 10);

    # When n is large enough, try to find a small factor
    if ($size > PRIMALITY_PRETEST_MIN) {

        state $g = Math::GMPz::Rmpz_init_nobless();

        my @checks = (1e3, 1e4, 1e5);

        if (INTSIZE >= 64 and $size >= 50_000) {
            push @checks, 1e7;
            push @checks, 1e8;
            push @checks, 1e9;
        }
        elsif ($size >= 9_000) {
            push @checks, 1e7;
            push @checks, 1e8;
        }
        elsif ($size >= 4_500) {
            push @checks, 1e6;
            push @checks, 1e7;
        }
        elsif ($size >= 4_000) {
            push @checks, 7968751;
        }
        elsif ($size >= 3_500) {
            push @checks, 5789445;
        }
        elsif ($size >= 3_000) {
            push @checks, 4201049;
        }
        elsif ($size >= 2_500) {
            push @checks, 2908076;
        }
        elsif ($size >= 2_000) {
            push @checks, 1670609;
        }
        elsif ($size >= 1_500) {
            push @checks, 908433;
        }

        foreach my $k (@checks) {

            say STDERR "[primality_pretest] Checking for prime factors < $k" if $VERBOSE;

            my $primorial = _cached_primorial($k);
            Math::GMPz::Rmpz_gcd($g, $primorial, $n);

            if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

                if ($VERBOSE) {
                    say STDERR "[primality_pretest] Composite with g = ", Math::GMPz::Rmpz_get_str($g, 10);
                }

                return 0;
            }
        }

        say STDERR "[primality_pretest] No small factor found..." if $VERBOSE;

        if ($size >= 3_000 and $USE_PFGW) {

            my ($res, $exit_code) = _execute_in_tmpdir("pfgw64 -q$n &> /dev/stdout");

            if (defined($res) and index($res, 'is composite') != -1) {
                say STDERR "[primality_pretest] PFGW: composite" if $VERBOSE;
                return 0;
            }

            if (defined($res) and $exit_code == 0) {
                say STDERR "[primality_pretest] PFGW: PRP found" if $VERBOSE;
                return 1;
            }
        }
    }

    return 1;
}

sub primality_pretest {
    my ($n) = @_;
    _primality_pretest($$n) ? $TRUE : $FALSE;
}

sub all_prime {
    my (@vals) = @_;
    _valid(\(@vals));

    foreach my $n (@vals) {
        _primality_pretest($$n) || return $FALSE;
    }

    my @strs;
    my %seen;

    foreach my $n (@vals) {
        my $str = ref($$n) ? (_big2uistr($$n) // return $FALSE) : $$n;
        next if $seen{$str}++;
        (
         (HAS_PRIME_UTIL and $str < ULONG_MAX)
         ? Math::Prime::Util::is_strong_pseudoprime($str, 2)
         : (CORE::length($str) > PRIMALITY_PRETEST_MIN ? Math::Prime::Util::GMP::is_strong_pseudoprime($str, 2) : Math::Prime::Util::GMP::is_prime($str))
        )
          || return $FALSE;
        push @strs, $str;
    }

    foreach my $n (@strs) {
        (
         (HAS_PRIME_UTIL and $n < ULONG_MAX)
         ? Math::Prime::Util::is_almost_extra_strong_lucas_pseudoprime($n)
         : (CORE::length($n) > PRIMALITY_PRETEST_MIN ? Math::Prime::Util::GMP::is_almost_extra_strong_lucas_pseudoprime($n) : 1)
        )
          || return $FALSE;
    }

    return $TRUE;
}

sub all_composite {
    my (@vals) = @_;
    _valid(\(@vals));

    my %seen;

    foreach my $n (@vals) {
        (_primality_pretest($$n) // return $FALSE) || next;
        my $str = _big2uistr($$n) // return $FALSE;
        next if $seen{$str}++;
        _is_prob_prime($str)
          && return $FALSE;
    }

    return $TRUE;
}

sub is_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_prime($n)
                        : Math::Prime::Util::GMP::is_prime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    _primality_pretest($n)
      && Math::Prime::Util::GMP::is_prime(_big2uistr($n) // return $FALSE) ? $TRUE : $FALSE;
}

sub is_emirp {
    my ($n, $base) = @_;

    $base //= TEN;
    my $n_rev = $n->flip($base);

    # Must not be a palindrome
    if ($n_rev->eq($n)) {
        return $FALSE;
    }

    $n_rev->all_prime($n);
}

sub is_gaussian_prime {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
    }
    else {
        $y = ZERO;
    }

    $x = _sanitize_mpz($$x) // return $FALSE;
    $y = _sanitize_mpz($$y) // return $FALSE;

    Math::Prime::Util::GMP::is_gaussian_prime($x, $y) ? $TRUE : $FALSE;
}

sub is_safe_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and HAS_NEW_PRIME_UTIL and !ref($n)) {
        return (Math::Prime::Util::is_safe_prime($n) ? $TRUE : $FALSE);
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    (Math::GMPz::Rmpz_odd_p($n) && _primality_pretest($n))
      || return $FALSE;

    my $t = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($t, $n, 1);
    Math::GMPz::Rmpz_div_2exp($t, $t, 1);

    _primality_pretest($t) || return $FALSE;

    (   Math::Prime::Util::GMP::is_strong_pseudoprime($t, 2)
     && Math::Prime::Util::GMP::is_strong_pseudoprime($n, 2)
     && Math::Prime::Util::GMP::is_almost_extra_strong_lucas_pseudoprime($t)) ? $TRUE : $FALSE;
}

sub _pollard_rho_factor {
    my ($r, $reps) = @_;

    my @factors;

    while (1) {
        my @f = Math::Prime::Util::GMP::pbrent_factor($r, $reps);

        if (scalar(@f) == 1) {
            @f = Math::Prime::Util::GMP::prho_factor($r, $reps);
        }

        $r = pop @f;
        my @new_factors;

        if (@f) {
            say STDERR "pbrent_factor(r, $reps): @f" if $VERBOSE;
            @new_factors = map { _is_prob_prime($_) ? $_ : _factor($_) } @f;
            push @factors, @new_factors;
        }

        foreach my $p (@new_factors) {
            my $v = Math::Prime::Util::GMP::valuation($r, $p);
            if ($v > 0) {
                push @factors, ($p) x $v;
                $r = Math::Prime::Util::GMP::divint($r, Math::Prime::Util::GMP::powint($p, $v));
            }
        }

        if ($r < $reps * $reps) {
            push @factors, _factor($r);
            $r = 1;
            last;
        }
        elsif ($reps >= 1e4 and _is_prob_prime($r)) {
            push @factors, $r;
            $r = 1;
            last;
        }

        @f or last;
    }

    return ($r, @factors);
}

sub _check_remainder_special {
    my ($remainder, $type) = @_;

    return _is_prob_prime($remainder) if $type eq 'almost';

    if (HAS_PRIME_UTIL && Math::GMPz::Rmpz_fits_ulong_p($remainder)) {
        return Math::Prime::Util::is_prime_power(Math::GMPz::Rmpz_get_ui($remainder));
    }

    return _prime_power_factor($remainder);
}

# Helper function to consolidate size threshold checks
sub _should_break_trial_division {
    my ($j, $r_size) = @_;

    return 1 if $j >= 4 && $r_size <= 83;                  # <= 25 digits
    return 1 if $j >= 5 && $r_size <= 100;                 # <= 30 digits
    return 1 if $j >= 6 && $r_size <= 133;                 # <= 40 digits
    return 1 if $j >= 7 && $r_size <= 150;                 # <= 45 digits
    return 1 if $j >= 8 && $r_size <= 200;                 # <= 60 digits
    return 1 if $j >= 8 && $r_size <= 250 && $USE_YAFU;    # <= 75 digits

    return 0;
}

sub _process_special_factors {
    my ($n_obj, $k, $count, $trial_limit, $trial_factors_ref, $j, $type) = @_;

    my @special_factors = @{$n_obj->special_factors(_set_int($j - 6))};
    my @gcd_factors     = @{$n_obj->gcd_factors(_array([@special_factors, map { _set_int($_) } @$trial_factors_ref]))};

    if ($type eq 'almost' && scalar(@gcd_factors) > $k) {
        return {result => 0};
    }

    my (@prime_factors, @composite_factors);

    # Separate prime and composite factors
    foreach my $f (@gcd_factors) {
        if (_is_prob_prime($$f)) {
            push @prime_factors, $f;
        }
        elsif (Math::GMPz::Rmpz_sizeinbase(_any2mpz($$f), 2) <= MEDIUM_NUMBER_MAX_BITS) {
            push @prime_factors, map { _set_int($_) } _factor($$f);
        }
        else {
            push @composite_factors, $f;
        }
    }

    if ($type eq 'almost' && (scalar(@prime_factors) + 2 * scalar(@composite_factors) > $k)) {
        return {result => 0};
    }

    my $prod      = Sidef::Types::Number::Number::prod(@prime_factors);
    my $c         = $n_obj->idiv($prod);
    my $remainder = _any2mpz($$c);

    @$trial_factors_ref =
      ($type eq 'omega')
      ? List::Util::uniq(map { "$$_" } @prime_factors)
      : map { $$_ } @prime_factors;

    $count = scalar(@$trial_factors_ref);

    return {result => ($count == $k)} if Math::GMPz::Rmpz_cmp_ui($remainder, 1) == 0;
    return {result => 0}              if $count >= $k;

    # Validate against remaining capacity
    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_str($t, "$trial_limit", 10);
    Math::GMPz::Rmpz_nextprime($t, $t);
    Math::GMPz::Rmpz_pow_ui($t, $t, $k - $count);
    return {result => 0} if Math::GMPz::Rmpz_cmp($remainder, $t) < 0;

    # Check for sufficiently large composite factors
    if ($j <= 8 && @composite_factors) {
        my $min_size = ($j == 8) ? FACTORDB_MIN : YAFU_MIN;
        if (Math::GMPz::Rmpz_sizeinbase(_any2mpz(${$composite_factors[-1]}), 10) > $min_size) {
            return {count => $count, remainder => $remainder, continue => 1};
        }
    }

    # Break down remaining composites
    foreach my $f (@composite_factors) {
        push @prime_factors, _factor($$f);
    }

    $count =
      ($type eq 'omega')
      ? scalar(List::Util::uniq(map { ref($_) ? "$$_" : $_ } @prime_factors))
      : scalar(@prime_factors);

    return {result => ($count == $k)};
}

sub _is_k_prime {
    my ($n_obj, $k, $type) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return $FALSE } : 2;

    # Handle trivial/special cases using the original object instance
    return $n_obj->is_one                                                if $k == 0;
    return $type eq 'almost' ? $n_obj->is_prime : $n_obj->is_prime_power if $k == 1;
    return $n_obj->is_semiprime                                          if $k == 2 && $type eq 'almost' && !ref($$n_obj);

    # Sanitize and assign to a new GMP value to preserve the $n_obj
    my $n_val = _sanitize_mpz($$n_obj) // return $FALSE;
    return $FALSE if Math::GMPz::Rmpz_sgn($n_val) <= 0;

    my $size = Math::GMPz::Rmpz_sizeinbase($n_val, 2);

    # Type-specific mathematical bounds checks
    if ($type eq 'almost') {
        return $FALSE if $k >= $size;    # Smallest k-almost prime is 2^k
    }
    elsif ($type eq 'omega') {
        return $FALSE if Math::GMPz::Rmpz_cmp($n_val, _cached_pn_primorial($k)) < 0;
    }
    else {
        die "Invalid type: <<$type>>";
    }

    # Fast path for small numbers using Math::Prime::Util
    if (HAS_PRIME_UTIL && Math::GMPz::Rmpz_fits_ulong_p($n_val)) {
        my $val = Math::GMPz::Rmpz_get_ui($n_val);
        my $result =
          ($type eq 'almost')
          ? Math::Prime::Util::is_almost_prime($k, $val)
          : Math::Prime::Util::is_omega_prime($k, $val);
        return $result ? $TRUE : $FALSE;
    }

    my $count     = 0;
    my $remainder = $n_val;
    my $min_size  = (INTSIZE <= 32) ? 32 : 40;

    # Heavy lifting for large numbers
    if ($size >= $min_size) {

        state $t = Math::GMPz::Rmpz_init_nobless();

        my @trial_factors;
        my $max_j = (INTSIZE <= 32) ? 8 : 9;

        foreach my $j (2 .. $max_j) {
            my $trial_limit = Math::Prime::Util::GMP::powint(10, $j);
            my ($r, @new_factors) = _primorial_trial_factor($remainder, $trial_limit);

            # Conjectured approach (Pollard's rho)
            my $max_conj_j = (INTSIZE <= 32) ? 5 : 7;
            if ($j <= $max_conj_j && $USE_CONJECTURES) {
                my $r_base10_size = Math::GMPz::Rmpz_sizeinbase($r, 10);

                if ($r_base10_size >= SPECIAL_FACTORS_MIN && $r_base10_size <= 500) {
                    if ($j <= 2) {
                        my ($rem, @f) = _pollard_rho_factor($r, 2 * $trial_limit);
                        $trial_limit = Math::Prime::Util::GMP::mulint($trial_limit, $trial_limit);
                        push @new_factors, @f;
                        $r = $rem;
                    }
                    else {
                        my $r_obj = _set_int($r);
                        my $lim   = $r_obj->iroot(_set_int($k - $count))->inc;
                        if (CORE::length("$lim") <= 20) {
                            $trial_limit = $lim;
                        }
                        else {
                            $trial_limit = _set_int($trial_limit)->sqr;
                        }

                        say STDERR "ECM trial limit: $trial_limit" if $VERBOSE;

                        my $f   = $r_obj->factor_upto($trial_limit);
                        my $rem = ${$f->pop};

                        push @new_factors, ($type eq 'omega') ? (map { "$$_" } @$f) : (map { $$_ } @$f);
                        $r = $rem;
                    }
                }
            }

            push @trial_factors, ($type eq 'omega') ? List::Util::uniq(@new_factors) : @new_factors;

            $count     = List::Util::max($count, scalar(@trial_factors));
            $remainder = _any2mpz($r);

            return ($count == $k ? $TRUE : $FALSE) if Math::GMPz::Rmpz_cmp_ui($remainder, 1) == 0;
            return $FALSE                          if $count >= $k;

            # Ensure remaining space is sufficient for needed prime factors
            Math::GMPz::Rmpz_set_str($t, "$trial_limit", 10);
            Math::GMPz::Rmpz_nextprime($t, $t);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k - $count);
            return $FALSE if Math::GMPz::Rmpz_cmp($remainder, $t) < 0;

            my $r_is_special = _check_remainder_special($remainder, $type);
            return ($count + 1 == $k ? $TRUE : $FALSE) if $r_is_special;
            return $FALSE if $count + ($r_is_special ? 1 : 2) > $k;

            # Size thresholds helper replaces the ladder of checks
            my $r_size = Math::GMPz::Rmpz_sizeinbase($remainder, 2);
            last if _should_break_trial_division($j, $r_size);

            # Try to find special factors for larger numbers
            if ($j >= 6) {

                # Safely pass the original $n_obj
                my $special_result = _process_special_factors($n_obj, $k, $count, $trial_limit, \@trial_factors, $j, $type);

                # Clean parsing based on the hash structure
                if (exists $special_result->{result}) {
                    return ($special_result->{result} ? $TRUE : $FALSE);
                }

                if (exists $special_result->{continue}) {
                    $count     = $special_result->{count};
                    $remainder = $special_result->{remainder};
                    next;
                }
            }
        }
    }

    return $FALSE if $count > $k;

    my @factors = ($type eq 'omega') ? _factor_exp($remainder) : _factor($remainder);
    $count += scalar(@factors);

    ($count == $k) ? $TRUE : $FALSE;
}

sub is_almost_prime {
    my ($n_obj, $k) = @_;
    _is_k_prime($n_obj, $k, 'almost');
}

sub is_omega_prime {
    my ($n_obj, $k) = @_;
    _is_k_prime($n_obj, $k, 'omega');
}

sub is_squarefree_almost_prime {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return $FALSE } : 2;

    if ($k == 0) {
        return $n->is_one;
    }
    elsif ($k == 1) {
        return $n->is_prime;
    }
    elsif ($k == 2) {
        if ($n->is_square) {
            return $FALSE;
        }
        return $n->is_semiprime;
    }

    my $n_obj = $n;

    $n = _sanitize_mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return $FALSE;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n);

        if (HAS_PRIME_UTIL) {
            if (Math::Prime::Util::is_almost_prime($k, $n) and Math::Prime::Util::is_square_free($n)) {
                return $TRUE;
            }
            return $FALSE;
        }

        if (Math::Prime::Util::is_square_free($n) and scalar(Math::Prime::Util::factor($n)) == $k) {
            return $TRUE;
        }
        return $FALSE;
    }

    my $size = Math::GMPz::Rmpz_sizeinbase($n, 2);

    if ($size > MEDIUM_NUMBER_MAX_BITS) {
        state $limit = _set_int(1e5);
        $n_obj->is_prob_squarefree($limit) || return $FALSE;
    }
    elsif ($size > SMALL_NUMBER_MAX_BITS) {
        state $limit = _set_int(1e4);
        $n_obj->is_prob_squarefree($limit) || return $FALSE;
    }
    elsif ($size > 64) {
        state $limit = _set_int(1e2);
        $n_obj->is_prob_squarefree($limit) || return $FALSE;
    }

    if ($n_obj->is_omega_prime(_set_int($k)) and $n_obj->is_squarefree) {
        return $TRUE;
    }

    return $FALSE;
}

sub is_prob_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_prime($n)
                        : Math::Prime::Util::GMP::is_prime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    _primality_pretest($n) || return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    (
       (CORE::length($n) > PRIMALITY_PRETEST_MIN)
     ? (Math::Prime::Util::GMP::is_strong_pseudoprime($n, 2) && Math::Prime::Util::GMP::is_frobenius_underwood_pseudoprime($n))
     : Math::Prime::Util::GMP::is_prime($n)
    ) ? $TRUE : $FALSE;
}

sub is_prov_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_prime($n)
                        : Math::Prime::Util::GMP::is_prime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    _primality_pretest($n)
      && Math::Prime::Util::GMP::is_provable_prime(_big2uistr($n) // return $FALSE) ? $TRUE : $FALSE;
}

*is_provable_prime = \&is_prov_prime;

sub is_bpsw_prime {
    my ($n) = @_;
    $n = $$n;
    _primality_pretest($n)
      && Math::Prime::Util::GMP::is_bpsw_prime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_bpsw_psp = \&is_bpsw_prime;
*is_bpsw_prp = \&is_bpsw_prime;

sub is_aks_prime {
    my ($n) = @_;
    $n = $$n;
    _primality_pretest($n)
      && Math::Prime::Util::GMP::is_aks_prime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

sub is_composite {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n <= 3
                   or (
                       HAS_PRIME_UTIL
                       ? Math::Prime::Util::is_prime($n)
                       : Math::Prime::Util::GMP::is_prime($n)
                      )
                ) ? $FALSE : $TRUE
               );
    }

    (_primality_pretest($n) // return $FALSE) || return $TRUE;
    _is_prob_prime(_big2uistr($n) // return $FALSE) ? $FALSE : $TRUE;
}

sub is_odd_composite {
    my ($n) = @_;
    $n->is_odd && $n->is_composite;
}

sub miller_rabin_random {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // 1 } : 1;
    $n = $$n;

    __is_int__($n)
      && Math::Prime::Util::GMP::miller_rabin_random(_big2uistr($n) // (return $FALSE), $k) ? $TRUE : $FALSE;
}

sub is_fermat_pseudoprime {
    my ($n, @bases) = @_;
    _valid(\(@bases));

    $n = $$n;

    if (ref($n)) {
        __is_int__($n) || return $FALSE;
    }

    if (scalar(@bases)) {
        @bases = map { ref($_) ? (_any2mpz($_) // return $FALSE) : $_ } map { $$_ } @bases;
    }
    else {
        @bases = (2);
    }

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($n)) {    # optimization
        foreach my $base (@bases) {

            $base == 1 && next;
            $base < 1  && next;
            $base < $n || next;

            Math::Prime::Util::gcd($n, $base) == 1
              || return $FALSE;

            Math::Prime::Util::is_pseudoprime($n, $base)
              || return $FALSE;
        }
    }

    $n = _any2mpz($n);

    Math::GMPz::Rmpz_sgn($n) > 0
      or return $FALSE;

    state $g = Math::GMPz::Rmpz_init_nobless();

    foreach my $base (@bases) {
        if (!ref($base)) {
            Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $n, CORE::abs($base)) == 1
              or return $FALSE;
        }
        else {
            Math::GMPz::Rmpz_gcd($g, $base, $n);
            Math::GMPz::Rmpz_cmp_ui($g, 1) == 0
              or return $FALSE;
        }
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sub_ui($g, $n, 1);

    foreach my $base (@bases) {
        ref($base)      ? Math::GMPz::Rmpz_set($t, $base)
          : ($base < 0) ? Math::GMPz::Rmpz_set_si($t, $base)
          :               Math::GMPz::Rmpz_set_ui($t, $base);
        Math::GMPz::Rmpz_powm($t, $t, $g, $n);
        Math::GMPz::Rmpz_cmp_ui($t, 1) == 0
          or return $FALSE;
    }

    return $TRUE;
}

*is_psp         = \&is_fermat_pseudoprime;
*is_fermat_psp  = \&is_fermat_pseudoprime;
*is_fermat_prp  = \&is_fermat_pseudoprime;
*is_pseudoprime = \&is_fermat_pseudoprime;

sub is_super_pseudoprime {
    my ($n, @bases) = @_;
    _valid(\(@bases));

    $n = $$n;
    __is_int__($n) || return $FALSE;

    my $z = _any2mpz($n) // return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    Math::Prime::Util::GMP::is_pseudoprime(
        $n,
        do {
            @bases = grep { defined($_) and $_ > 1 } map { _big2uistr($$_) } @bases;
            @bases ? (@bases) : (2);
        }
    ) || return $FALSE;

    @bases = (2) if !@bases;

    my $check_conditions = sub {

        # Using Thomas Ordowski's criterion from A050217.
#<<<
        my $gcd = Math::Prime::Util::GMP::gcd(
            map {
                ($_ < ~0)
                    ? ($_ - 1)
                    : Math::Prime::Util::GMP::subint($_, 1)
            } @_
        );
#>>>

        foreach my $base (@bases) {
            Math::Prime::Util::GMP::powmod($base, $gcd, $n) eq '1'
              or return;
        }

        return 1;
    };

    my @factors = map { ref($_) ? Math::GMPz::Rmpz_get_str($_, 10) : $_ } _miller_factor($z);

    if (scalar(@factors) == 1 and Math::GMPz::Rmpz_sizeinbase($z, 10) > SPECIAL_FACTORS_MIN) {
        @factors = map { $$_ } @{_set_int($z)->special_factors};
    }

    if (scalar(@factors) > 1) {

        my @primes;
        my @composites;

        foreach my $f (@factors) {

            foreach my $base (@bases) {
                Math::Prime::Util::GMP::powmod($base, $f, $n) eq $base
                  or return $FALSE;
            }

            if (_is_prob_prime($f)) {
                push @primes, $f;
            }
            else {
                push @composites, $f;
            }
        }

        if (scalar(@primes) > 1) {
            $check_conditions->(@primes)
              || return $FALSE;
        }

        @composites
          || return $TRUE;
    }

    @factors = map { _factor($_) } @factors;

    $check_conditions->(@factors) ? $TRUE : $FALSE;
}

*is_super_psp        = \&is_super_pseudoprime;
*is_superpseudoprime = \&is_super_pseudoprime;

# A141232 for b = 2;
# A141350 for b = 3;
# A140658 for b = 2,3;

sub is_over_pseudoprime {
    my ($n, @bases) = @_;
    _valid(\(@bases));

    $n = $$n;
    __is_int__($n) || return $FALSE;

    my $z = _any2mpz($n) // return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    Math::Prime::Util::GMP::is_strong_pseudoprime(
        $n,
        do {
            @bases = grep { defined($_) and $_ > 1 } map { _big2uistr($$_) } @bases;
            @bases ? (@bases) : (2);
        }
    ) || return $FALSE;

    @bases = (2) if !@bases;

    my $check_conditions = sub {

#<<<
        my $gcd = Math::Prime::Util::GMP::gcd(
            map {
                ($_ < ~0)
                    ? ($_ - 1)
                    : Math::Prime::Util::GMP::subint($_, 1)
            } @_
        );
#>>>

        foreach my $base (@bases) {
            Math::Prime::Util::GMP::powmod($base, $gcd, $n) eq '1'
              or return;
        }

        scalar(@_) > 1 or return 1;

        my %znorder;

        foreach my $p (@_) {
            foreach my $base (@bases) {

                if (exists $znorder{$base}) {
                    if (HAS_PRIME_UTIL and $p < ULONG_MAX and $base < ULONG_MAX) {
                        Math::Prime::Util::powmod($base, $znorder{$base}, $p) == 1 or return;
                    }
                    else {
                        Math::Prime::Util::GMP::powmod($base, $znorder{$base}, $p) eq '1' or return;
                    }
                }

                my $zn =
                  (HAS_PRIME_UTIL and $p < ULONG_MAX and $base < ULONG_MAX)
                  ? Math::Prime::Util::znorder($base, $p)
                  : Math::Prime::Util::GMP::znorder($base, $p);

                if (exists $znorder{$base}) {
                    $znorder{$base} eq $zn or return;
                }
                else {
                    $znorder{$base} = $zn;
                }
            }
        }

        return 1;
    };

    my @factors = map { ref($_) ? Math::GMPz::Rmpz_get_str($_, 10) : $_ } _miller_factor($z);

    if (scalar(@factors) == 1 and Math::GMPz::Rmpz_sizeinbase($z, 10) > SPECIAL_FACTORS_MIN) {
        @factors = map { $$_ } @{_set_int($z)->special_factors};
    }

    if (scalar(@factors) > 1) {

        my @primes;
        my @composites;

        foreach my $f (@factors) {

            foreach my $base (@bases) {
                Math::Prime::Util::GMP::powmod($base, $f, $n) eq $base
                  or return $FALSE;
            }

            if (_is_prob_prime($f)) {
                push @primes, $f;
            }
            else {
                push @composites, $f;
            }
        }

        if (scalar(@primes) > 1) {
            $check_conditions->(@primes)
              || return $FALSE;
        }

        @composites
          || return $TRUE;
    }

    @factors = map { _factor($_) } @factors;

    $check_conditions->(@factors) ? $TRUE : $FALSE;
}

*is_over_psp        = \&is_over_pseudoprime;
*is_overpseudoprime = \&is_over_pseudoprime;

sub is_euler_pseudoprime {
    my ($n, @bases) = @_;
    _valid(\(@bases));

    $n = $$n;

    if (ref($n)) {
        __is_int__($n) || return $FALSE;
    }

    if (scalar(@bases)) {
        @bases = map { ref($_) ? (_any2mpz($_) // return $FALSE) : $_ } map { $$_ } @bases;
    }
    else {
        @bases = (2);
    }

    if (FAST_MODE and !ref($n) and scalar(@bases) == 1 and $bases[0] > 1 and $bases[0] < ULONG_MAX) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_euler_pseudoprime($n, $bases[0])
                        : Math::Prime::Util::GMP::is_euler_pseudoprime($n, $bases[0])
                       )
                ) ? $TRUE : $FALSE
               );
    }
    elsif (FAST_MODE and HAS_PRIME_UTIL and !ref($n) and scalar(grep { $_ > 1 and $_ < ULONG_MAX } @bases) == scalar(@bases)) {
        return (($n > 1 and Math::Prime::Util::is_euler_pseudoprime($n, @bases)) ? $TRUE : $FALSE);
    }

    # Any value > 1 is an Euler-Jacobi pseudoprime to base 1
    if (scalar(@bases) == 1 and $bases[0] == 1) {
        return (($n > 1) ? $TRUE : $FALSE);
    }

    Math::Prime::Util::GMP::is_euler_pseudoprime(_big2pistr($n) // (return $FALSE), (grep { $_ ne '1' } map { _big2pistr($_) // return $FALSE } @bases))
      ? $TRUE
      : $FALSE;
}

*is_euler_psp = \&is_euler_pseudoprime;
*is_euler_prp = \&is_euler_pseudoprime;

sub is_strong_fermat_pseudoprime {
    my ($n, @bases) = @_;
    _valid(\(@bases));

    $n = $$n;

    if (ref($n)) {
        __is_int__($n) || return $FALSE;
    }

    if (scalar(@bases)) {
        @bases = map { ref($_) ? (_any2mpz($_) // return $FALSE) : $_ } map { $$_ } @bases;
    }
    else {
        @bases = (2);
    }

    if (FAST_MODE and !ref($n)) {

        $n < 1 and return $FALSE;

        foreach my $base (@bases) {

            $base < 1  and return $FALSE;
            $base == 1 and next;

            $base == $n
              && return $FALSE;

            (
             HAS_PRIME_UTIL
             ? Math::Prime::Util::gcd($n, $base)
             : Math::Prime::Util::GMP::gcd($n, $base)
              ) == 1
              || return $FALSE;

            (
             (HAS_PRIME_UTIL and $base < ULONG_MAX)
             ? Math::Prime::Util::is_strong_pseudoprime($n, $base)
             : Math::Prime::Util::GMP::is_strong_pseudoprime($n, $base)
            )
              || return $FALSE;
        }

        return $TRUE;
    }

    $n = _big2pistr($n) // return $FALSE;

    foreach my $base (map { _big2pistr($_) // return $FALSE } @bases) {

        $base eq '1' and next;

        $base eq $n
          && return $FALSE;

        Math::Prime::Util::GMP::gcd($n, $base) eq '1'
          || return $FALSE;

        Math::Prime::Util::GMP::is_strong_pseudoprime($n, $base)
          || return $FALSE;
    }

    return $TRUE;
}

*is_spsp               = \&is_strong_fermat_pseudoprime;
*miller_rabin          = \&is_strong_fermat_pseudoprime;
*is_strong_psp         = \&is_strong_fermat_pseudoprime;
*is_strong_prp         = \&is_strong_fermat_pseudoprime;
*is_strong_fermat_psp  = \&is_strong_fermat_pseudoprime;
*is_strong_fermat_prp  = \&is_strong_fermat_pseudoprime;
*is_strong_pseudoprime = \&is_strong_fermat_pseudoprime;

sub is_chebyshev_pseudoprime {    # OEIS: A175530
    my ($n) = @_;

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 7056721) < 0 and return $FALSE;
    Math::GMPz::Rmpz_odd_p($n) or return $FALSE;

    my $nstr = Math::GMPz::Rmpz_get_str($n, 10);

    # V_n(P,1) == P (mod n) for any integer P.
    foreach my $i (1 .. 10) {    # test with random values of P

        my $P = CORE::int(CORE::rand($LUCAS_PQ_LIMIT - 11)) + 11;
        my $V = _modular_lucas_V($P, 1, $n, $n);

        if ($V != ($P % $n)) {
            return $FALSE;
        }

        if ($i == 1 and _is_prob_prime($nstr)) {
            return $FALSE;
        }
    }

    # V_n(P,1) == P (mod n) for any integer P.
    foreach my $P (1, 3 .. 10) {    # test with small P
        my $V = _modular_lucas_V($P, 1, $n, $n);

        if ($V != ($P % $n)) {
            return $FALSE;
        }
    }

    # Odd composite integer n is a Chebyshev pseudoprime iff:
    #       n == {+1,-1} (mod p-1)
    #       n == {+1,-1} (mod p+1)
    # for each prime p|n.

    my @factors = _factor($nstr);

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($t, $n, 1);
    Math::GMPz::Rmpz_add_ui($u, $n, 1);

    foreach my $p (@factors) {

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($v, $p)
          : Math::GMPz::Rmpz_set_str($v, $p, 10);

        Math::GMPz::Rmpz_sub_ui($v, $v, 1);
        Math::GMPz::Rmpz_divisible_p($t, $v)
          || Math::GMPz::Rmpz_divisible_p($u, $v)
          || return $FALSE;

        Math::GMPz::Rmpz_add_ui($v, $v, 2);
        Math::GMPz::Rmpz_divisible_p($u, $v)
          || Math::GMPz::Rmpz_divisible_p($t, $v)
          || return $FALSE;
    }

    return $TRUE;
}

*is_chebyshev     = \&is_chebyshev_pseudoprime;
*is_chebyshev_psp = \&is_chebyshev_pseudoprime;

sub is_lucasU_pseudoprime {    # true if U_n(P,Q) == 0 mod n
    my ($n, $P, $Q) = @_;

    __is_int__($$n) || return $FALSE;

    $P = defined($P) ? do { ref($P) eq __PACKAGE__ or _valid(\$P); _any2mpz($$P) // return $FALSE } : +1;
    $Q = defined($Q) ? do { ref($Q) eq __PACKAGE__ or _valid(\$Q); _any2mpz($$Q) // return $FALSE } : -1;

    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return $FALSE;

    my $k = 0;
    my $D = $P * $P - 4 * $Q;

    if (ref($D)) {
        Math::GMPz::Rmpz_perfect_square_p($D) && return $FALSE;
        $k = Math::GMPz::Rmpz_kronecker($D, $n) || return $FALSE;
    }
    else {
        Math::Prime::Util::GMP::is_square($D) && return $FALSE;
        $k = Math::Prime::Util::GMP::kronecker($D, $n) || return $FALSE;
    }

    my $U = _modular_lucas_U($P, $Q, $n - $k, $n);

    ($U == 0) ? $TRUE : $FALSE;
}

*is_lucasu_psp            = \&is_lucasU_pseudoprime;
*is_lucasU_psp            = \&is_lucasU_pseudoprime;
*is_fib_psp               = \&is_lucasU_pseudoprime;
*is_fib_prp               = \&is_lucasU_pseudoprime;
*is_fibonacci_psp         = \&is_lucasU_pseudoprime;
*is_fibonacci_prp         = \&is_lucasU_pseudoprime;
*is_fibonacci_pseudoprime = \&is_lucasU_pseudoprime;

sub is_lucasV_pseudoprime {    # true if V_n(P,Q) == P mod n
    my ($n, $P, $Q) = @_;

    __is_int__($$n) || return $FALSE;

    $P = defined($P) ? do { ref($P) eq __PACKAGE__ or _valid(\$P); _any2mpz($$P) // return $FALSE } : 1;
    $Q = defined($Q) ? do { ref($Q) eq __PACKAGE__ or _valid(\$Q); _any2mpz($$Q) // return $FALSE } : -1;

    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return $FALSE;

    my $V = _modular_lucas_V($P, $Q, $n, $n);

    ($V == ($P % $n)) ? $TRUE : $FALSE;
}

*is_lucasv_psp                 = \&is_lucasV_pseudoprime;
*is_lucasv_prp                 = \&is_lucasV_pseudoprime;
*is_lucasV_psp                 = \&is_lucasV_pseudoprime;
*is_lucasV_prp                 = \&is_lucasV_pseudoprime;
*is_bruckman_lucas_psp         = \&is_lucasV_pseudoprime;
*is_bruckman_lucas_prp         = \&is_lucasV_pseudoprime;
*is_bruckman_lucas_pseudoprime = \&is_lucasV_pseudoprime;

sub is_pell_lucas_pseudoprime {    # OEIS: A270342 (primes + composites), A270345 (composites)
    my ($n) = @_;

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 2) > 0
      or return $FALSE;

    my $V = _modular_lucas_V(2, -1, $n, $n);
    $V == 2 ? $TRUE : $FALSE;
}

*is_pell_lucas_psp = \&is_pell_lucas_pseudoprime;
*is_pell_lucas_prp = \&is_pell_lucas_pseudoprime;

sub is_pell_pseudoprime {    # OEIS: A099011 (odd composites)
    my ($n) = @_;

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 2) > 0 or return $FALSE;
    Math::GMPz::Rmpz_even_p($n) and return $FALSE;

    my $U = _modular_lucas_U(2, -1, $n, $n);

    if (Math::GMPz::Rmpz_ui_kronecker(2, $n) == 1) {
        return (($U == 1) ? $TRUE : $FALSE);
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    if (ref($U)) {
        Math::GMPz::Rmpz_add_ui($t, $U, 1);
    }
    else {
        Math::GMPz::Rmpz_set_ui($t, $U + 1);
    }

    Math::GMPz::Rmpz_cmp($t, $n) ? $FALSE : $TRUE;
}

*is_pell_psp = \&is_pell_pseudoprime;
*is_pell_prp = \&is_pell_pseudoprime;

sub is_strong_fibonacci_pseudoprime {
    my ($n) = @_;

    # A strong Fibonacci pseudoprime is a composite number n which satisfies the following congruence with Q = -1 and all P:
    #   V_n(P,Q) = P (mod n)

    # The first several strong Fibonacci pseudoprimes, are:
    #   443372888629441, 39671149333495681, 842526563598720001,
    #   2380296518909971201, 3188618003602886401, 33711266676317630401

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    state $min = Math::GMPz::Rmpz_init_set_str_nobless("443372888629441", 10);

    Math::GMPz::Rmpz_cmp($n, $min) < 0 and return $FALSE;
    Math::GMPz::Rmpz_odd_p($n) or return $FALSE;

    my $nstr = Math::GMPz::Rmpz_get_str($n, 10);

    # Check if n is a Fermat pseudoprime to base-2.
    Math::Prime::Util::GMP::is_pseudoprime($nstr, 2)
      || return $FALSE;

    # V_n(P,-1) == P (mod n) for any integer P.
    foreach my $i (1 .. 10) {    # test with random values of P

        my $P = CORE::int(CORE::rand($LUCAS_PQ_LIMIT - 11)) + 11;
        my $V = _modular_lucas_V($P, -1, $n, $n);

        if ($V != ($P % $n)) {
            return $FALSE;
        }

        if ($i == 1 and _is_prob_prime($nstr)) {
            return $FALSE;
        }
    }

    # V_n(P,-1) == P (mod n) for any integer P.
    foreach my $P (1, 3 .. 10) {    # test with small P
        my $V = _modular_lucas_V($P, -1, $n, $n);

        if ($V != ($P % $n)) {
            return $FALSE;
        }
    }

    # Odd composite integer n is a strong Fibonacci pseudoprime iff:
    #     1) n is a Carmichael number: p-1 | n-1
    #     2) 2(p + 1) | (n − 1) or 2(p + 1) | (n − p)
    # for each prime p|n.

    my @factors = _factor($nstr);

    state $nm1 = Math::GMPz::Rmpz_init_nobless();
    state $u   = Math::GMPz::Rmpz_init_nobless();
    state $v   = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($nm1, $n, 1);

    my %seen;

    foreach my $p (@factors) {

        if ($seen{$p}++) {    # not squarefree
            return $FALSE;
        }

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($v, $p)
          : Math::GMPz::Rmpz_set_str($v, $p, 10);

        # Check Korselt's criterion for Carmichael numbers:
        #   p-1 | n-1, for each p|n.

        Math::GMPz::Rmpz_sub_ui($u, $v, 1);
        Math::GMPz::Rmpz_divisible_p($nm1, $u) || return $FALSE;

        # Check if any of the following condition is satisfied:
        #    2(p + 1) | (n − 1)
        #    2(p + 1) | (n − p)

        Math::GMPz::Rmpz_sub($u, $n, $v);
        Math::GMPz::Rmpz_add_ui($v, $v, 1);
        Math::GMPz::Rmpz_mul_2exp($v, $v, 1);

        Math::GMPz::Rmpz_divisible_p($nm1, $v)
          || Math::GMPz::Rmpz_divisible_p($u, $v)
          || return $FALSE;
    }

    return $TRUE;
}

*is_strong_fib           = \&is_strong_fibonacci_pseudoprime;
*is_strong_fibonacci     = \&is_strong_fibonacci_pseudoprime;
*is_strong_fib_psp       = \&is_strong_fibonacci_pseudoprime;
*is_strong_fibonacci_psp = \&is_strong_fibonacci_pseudoprime;

sub is_bfw_psp {    # OEIS: A365514
    my ($n) = @_;

    # Reference:
    #   Strengthening the Baillie-PSW primality test
    #   https://arxiv.org/abs/2006.14425

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) <= 0
      && return $FALSE;

    if (Math::GMPz::Rmpz_even_p($n)) {
        return $TRUE if (Math::GMPz::Rmpz_cmp_ui($n, 2) == 0);
        return $FALSE;
    }

    my $P = 1;
    my $Q = undef;

    # Find first D for which kronecker(D, n) == -1
    for (my $k = 2 ; ; ++$k) {
        my $D = (-1)**$k * (2 * $k + 1);
        my $K = Math::GMPz::Rmpz_si_kronecker($D, $n);

        if ($K == 0) {
            my $g = Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $n, CORE::abs($D));
            if ($g > 1 and Math::GMPz::Rmpz_cmp_ui($n, $g) > 0) {
                return $FALSE;
            }
        }

        if ($k == 20 and Math::GMPz::Rmpz_perfect_square_p($n)) {
            return $FALSE;
        }

        if ($K == -1) {
            $Q =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divint(1 - $D, 4)
              : Math::Prime::Util::GMP::divint(1 - $D, 4);
            last;
        }
    }

    if ($Q == -1) {
        $P = 5;
        $Q = 5;
    }

    state $t = Math::GMPz::Rmpz_init();
    state $u = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_add_ui($t, $n, 1);

    my $V = _modular_lucas_V($P, $Q, $t, $n);
    ref($V)
      ? Math::GMPz::Rmpz_set($t, $V)
      : Math::GMPz::Rmpz_set_ui($t, $V);
    Math::GMPz::Rmpz_set_si($u, 2 * $Q);
    Math::GMPz::Rmpz_congruent_p($t, $u, $n) ? $TRUE : $FALSE;
}

*is_vpsp = \&is_bfw_psp;

sub is_bfsw_psp {    # a slightly stronger and faster BFW test
    my ($n) = @_;

    # No counter-examples are known to this test.
    # Also the test is not correlated with the BFW test, using different P and Q.

    # See also:
    #   Strengthening the Baillie-PSW primality test
    #   https://arxiv.org/abs/2006.14425

    __is_int__($$n) || return $FALSE;
    $n = _any2mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) <= 0
      && return $FALSE;

    if (Math::GMPz::Rmpz_even_p($n)) {
        return $TRUE if (Math::GMPz::Rmpz_cmp_ui($n, 2) == 0);
        return $FALSE;
    }

    my $P = undef;
    my $Q = -2;

    # Find the first P >= 2 such that D = P^2 - 4*Q has kronecker(D, n) == -1
    for ($P = 2 ; ; ++$P) {
        my $D = $P * $P - 4 * $Q;

        my $K = Math::GMPz::Rmpz_si_kronecker($D, $n);

        if ($K == -1) {
            last;
        }
        elsif ($K == 0 and Math::GMPz::Rmpz_cmp_ui($n, CORE::abs($D)) > 0) {
            return $FALSE;
        }
        elsif ($P == 20 and Math::GMPz::Rmpz_perfect_square_p($n)) {
            return $FALSE;
        }
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_add_ui($t, $n, 1);

    my $s = Math::GMPz::Rmpz_scan1($t, 0);
    Math::GMPz::Rmpz_div_2exp($t, $t, $s + 1);

    state $V1 = Math::GMPz::Rmpz_init_nobless();
    state $V2 = Math::GMPz::Rmpz_init_nobless();

    state $Q1 = Math::GMPz::Rmpz_init_nobless();
    state $Q2 = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($V1, 2);
    Math::GMPz::Rmpz_set_ui($V2, $P);

    Math::GMPz::Rmpz_set_ui($Q1, 1);
    Math::GMPz::Rmpz_set_ui($Q2, 1);

    my $nbits = Math::GMPz::Rmpz_sizeinbase($t, 2);
    for (my $i = $nbits - 1 ; $i >= 0 ; $i--) {
        my $bit = Math::GMPz::Rmpz_tstbit($t, $i);

        Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
        Math::GMPz::Rmpz_mod($Q1, $Q1, $n);

        if ($bit) {
            Math::GMPz::Rmpz_mul_si($Q2, $Q1, $Q);
            Math::GMPz::Rmpz_mul($V1, $V1, $V2);
            Math::GMPz::Rmpz_powm_ui($V2, $V2, 2, $n);
            Math::GMPz::Rmpz_submul_ui($V1, $Q1, $P);
            Math::GMPz::Rmpz_submul_ui($V2, $Q2, 2);
            Math::GMPz::Rmpz_mod($V1, $V1, $n);
        }
        else {
            Math::GMPz::Rmpz_set($Q2, $Q1);
            Math::GMPz::Rmpz_mul($V2, $V2, $V1);
            Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $n);
            Math::GMPz::Rmpz_submul_ui($V2, $Q1, $P);
            Math::GMPz::Rmpz_submul_ui($V1, $Q2, 2);
            Math::GMPz::Rmpz_mod($V2, $V2, $n);
        }
    }

    Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
    Math::GMPz::Rmpz_mod($Q1, $Q1, $n);

    Math::GMPz::Rmpz_mul_si($Q2, $Q1, $Q);
    Math::GMPz::Rmpz_mul($V1, $V1, $V2);
    Math::GMPz::Rmpz_submul_ui($V1, $Q1, $P);
    Math::GMPz::Rmpz_mul($Q2, $Q2, $Q1);

    for (1 .. $s) {
        Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $n);
        Math::GMPz::Rmpz_submul_ui($V1, $Q2, 2);
        Math::GMPz::Rmpz_powm_ui($Q2, $Q2, 2, $n);
    }

    Math::GMPz::Rmpz_set_si($t, 2 * $Q);
    Math::GMPz::Rmpz_congruent_p($V1, $t, $n) || return $FALSE;

    # Known counter-examples if we return true here:
    #   245, 1957, 5256479

    Math::GMPz::Rmpz_set_si($t, $Q * $Q);
    Math::GMPz::Rmpz_congruent_p($Q2, $t, $n) || return $FALSE;

    return $TRUE;
}

sub is_lucas_pseudoprime {
    my ($n, $P, $Q) = @_;

    if (defined($P) or defined($Q)) {
        return $n->is_lucasU_pseudoprime($P, $Q);
    }

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_lucas_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_lucas_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_lucas_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_lpsp          = \&is_lucas_pseudoprime;
*is_lucas_psp     = \&is_lucas_pseudoprime;
*is_lucas_prp     = \&is_lucas_pseudoprime;
*is_selfridge_prp = \&is_lucas_pseudoprime;

sub is_strong_lucas_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_strong_lucas_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_strong_lucas_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_strong_lucas_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_slpsp                = \&is_strong_lucas_pseudoprime;
*is_strong_lucas_psp     = \&is_strong_lucas_pseudoprime;
*is_strong_lucas_prp     = \&is_strong_lucas_pseudoprime;
*is_strong_selfridge_prp = \&is_strong_lucas_pseudoprime;

sub is_stronger_lucas_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_extra_strong_lucas_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_extra_strong_lucas_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_extra_strong_lucas_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_stronger_lucas_psp             = \&is_stronger_lucas_pseudoprime;
*is_stronger_lucas_prp             = \&is_stronger_lucas_pseudoprime;
*is_extra_strong_lucas_psp         = \&is_stronger_lucas_pseudoprime;
*is_extra_strong_lucas_prp         = \&is_stronger_lucas_pseudoprime;
*is_extra_strong_lucas_pseudoprime = \&is_stronger_lucas_pseudoprime;

sub is_strongish_lucas_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_almost_extra_strong_lucas_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_almost_extra_strong_lucas_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_almost_extra_strong_lucas_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_strongish_lucas_psp = \&is_strongish_lucas_pseudoprime;
*is_strongish_lucas_prp = \&is_strongish_lucas_pseudoprime;

sub is_plumb_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_euler_plumb_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_euler_plumb_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_euler_plumb_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_plumb_psp               = \&is_plumb_pseudoprime;
*is_plumb_prp               = \&is_plumb_pseudoprime;
*is_euler_plumb_psp         = \&is_plumb_pseudoprime;
*is_euler_plumb_prp         = \&is_plumb_pseudoprime;
*is_euler_plumb_pseudoprime = \&is_plumb_pseudoprime;

sub is_perrin_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_perrin_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_perrin_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_perrin_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_perrin_psp = \&is_perrin_pseudoprime;
*is_perrin_prp = \&is_perrin_pseudoprime;

sub is_frobenius_pseudoprime {
    my ($n, $k, $m) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        ref($m) eq __PACKAGE__ or _valid(\$m);
    }

    $n = $$n;

    __is_int__($n)
      && (
          defined($k)
          ? Math::Prime::Util::GMP::is_frobenius_pseudoprime(
                                                             _big2uistr($n) // (return $FALSE),
                                                             (defined($k) ? _big2istr($$k) // () : ()),
                                                             (defined($m) ? _big2istr($$m) // () : ()),
                                                            )
          : Math::Prime::Util::GMP::is_frobenius_pseudoprime(_big2uistr($n) // (return $FALSE))
         ) ? $TRUE : $FALSE;
}

*is_frobenius_psp = \&is_frobenius_pseudoprime;
*is_frobenius_prp = \&is_frobenius_pseudoprime;

sub is_frobenius_underwood_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_frobenius_underwood_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_frobenius_underwood_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_frobenius_underwood_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_underwood_psp           = \&is_frobenius_underwood_pseudoprime;
*is_underwood_prp           = \&is_frobenius_underwood_pseudoprime;
*is_underwood_pseudoprime   = \&is_frobenius_underwood_pseudoprime;
*is_frobenius_underwood_psp = \&is_frobenius_underwood_pseudoprime;
*is_frobenius_underwood_prp = \&is_frobenius_underwood_pseudoprime;

sub is_frobenius_khashin_pseudoprime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_frobenius_khashin_pseudoprime($n)
                        : Math::Prime::Util::GMP::is_frobenius_khashin_pseudoprime($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    __is_int__($n)
      && Math::Prime::Util::GMP::is_frobenius_khashin_pseudoprime(_big2uistr($n) // (return $FALSE)) ? $TRUE : $FALSE;
}

*is_khashin_psp           = \&is_frobenius_khashin_pseudoprime;
*is_khashin_prp           = \&is_frobenius_khashin_pseudoprime;
*is_khashin_pseudoprime   = \&is_frobenius_khashin_pseudoprime;
*is_frobenius_khashin_psp = \&is_frobenius_khashin_pseudoprime;
*is_frobenius_khashin_prp = \&is_frobenius_khashin_pseudoprime;

sub is_nminus1_prime {
    my ($n) = @_;

    $n = $$n;
    __is_int__($n) || return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    _is_prob_prime($n)
      && Math::Prime::Util::GMP::is_nminus1_prime($n) ? $TRUE : $FALSE;
}

*is_nm1_prime = \&is_nminus1_prime;
*is_pm1_prime = \&is_nminus1_prime;

sub is_nplus1_prime {
    my ($n) = @_;

    $n = $$n;
    __is_int__($n) || return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    _is_prob_prime($n)
      && Math::Prime::Util::GMP::is_nplus1_prime($n) ? $TRUE : $FALSE;
}

*is_np1_prime = \&is_nplus1_prime;
*is_pp1_prime = \&is_nplus1_prime;

sub is_llr_prime {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2ui($$n)  // return undef;
    $k = _any2mpz($$k) // return $FALSE;

    my $t = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_setbit($t, $n);

    # k must be smaller than 2^n
    my $valid = 1;
    if (Math::GMPz::Rmpz_cmp($k, $t) >= 0) {
        $valid = 0;
    }

    # t = k * 2^n - 1
    Math::GMPz::Rmpz_mul($t, $t, $k);
    Math::GMPz::Rmpz_sub_ui($t, $t, 1);

    $valid
      || return ((bless \$t)->is_prob_prime);

    _primality_pretest($t) || return $FALSE;

    my $r = Math::Prime::Util::GMP::is_llr_prime(Math::GMPz::Rmpz_get_str($t, 10));

    if ($r < 0) {
        return ((bless \$t)->is_prob_prime);
    }

    ($r >= 1) ? $TRUE : $FALSE;
}

sub is_proth_prime {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2ui($$n)  // return undef;
    $k = _any2mpz($$k) // return $FALSE;

    my $t = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_setbit($t, $n);

    # k must be smaller than 2^n
    my $valid = 1;
    if (Math::GMPz::Rmpz_cmp($k, $t) >= 0) {
        $valid = 0;
    }

    # t = k * 2^n + 1
    Math::GMPz::Rmpz_mul($t, $t, $k);
    Math::GMPz::Rmpz_add_ui($t, $t, 1);

    $valid
      || return ((bless \$t)->is_prob_prime);

    _primality_pretest($t) || return $FALSE;

    my $r = Math::Prime::Util::GMP::is_proth_prime(Math::GMPz::Rmpz_get_str($t, 10));

    if ($r < 0) {
        return ((bless \$t)->is_prob_prime);
    }

    ($r >= 1) ? $TRUE : $FALSE;
}

sub is_ecpp_prime {
    my ($n) = @_;

    $n = $$n;

    __is_int__($n) || return $FALSE;
    $n = _big2uistr($n) // return $FALSE;

    _is_prob_prime($n)
      && Math::Prime::Util::GMP::is_ecpp_prime($n) ? $TRUE : $FALSE;
}

{
    # Note: This list should be updated whenever new Mersenne primes are found.
    # Refer: https://www.mersenne.org/
    my %MERSENNE_PRIME_EXPONENTS;
    @MERSENNE_PRIME_EXPONENTS{
        (
            2,        3,        5,        7,        13,       17,       19,       31,       61,       89,       107,      127,
            521,      607,      1279,     2203,     2281,     3217,     4253,     4423,     9689,     9941,     11213,    19937,
            21701,    23209,    44497,    86243,    110503,   132049,   216091,   756839,   859433,   1257787,  1398269,  2976221,
            3021377,  6972593,  13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609, 57885161,
            74207281, 77232917, 82589933, 136279841
        )
    } = ();

    sub is_mersenne_prime {
        my ($n) = @_;

        $n = $$n;

        if (ref($n)) {
            __is_int__($n) || return $FALSE;
            $n = _big2pistr($n) // return $FALSE;
        }

        if (exists $MERSENNE_PRIME_EXPONENTS{$n}) {
            return $TRUE;
        }

        if ($n < 69_369_389 or !_is_prob_prime($n)) {

            # According to GIMPS, all exponents below 69369389 have been tested and verified. (2024-10-23)
            # https://www.mersenne.org/report_milestones/
            return $FALSE;
        }

        if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
            return (Math::Prime::Util::is_mersenne_prime($n) ? $TRUE : $FALSE);
        }

        Math::Prime::Util::GMP::is_mersenne_prime($n) ? $TRUE : $FALSE;
    }
}

sub primes_each {
    my ($from, $to, $block) = @_;

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return undef;
        $to   = _any2mpz($$to)   // return undef;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return undef;
        $from  = $TWO;
    }

    if (Math::GMPz::Rmpz_cmp_ui($from, 1) <= 0) {
        $from = $TWO;
    }

#<<<
    _generic_each(
        $from, $to, $block,
        sub {
            my ($from) = @_;

            my $t    = Math::GMPz::Rmpz_get_d($from);
            my $step = _nth_prime_lower($t + CORE::log($t) * 2e3) - _nth_prime_lower($t);

            if ($step <= 0 or $step > 1e6) {
                $step = 1e6;
            }

            $step;
        },
        sub { _primes($_[0], $_[1]) }
    );
#>>>
}

*each_prime = \&primes_each;

sub composites_each {
    my ($from, $to, $block) = @_;

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return undef;
        $to   = _any2mpz($$to)   // return undef;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return undef;
        $from  = $TWO + $TWO;
    }

    if (Math::GMPz::Rmpz_cmp_ui($from, 3) <= 0) {
        $from = $TWO + $TWO;
    }

    _generic_each(
        $from, $to, $block,
        sub {
            1e5;
        },
        sub {
            my ($from, $to) = @_;

            if (ref($to) eq 'Math::GMPz' and Math::GMPz::Rmpz_fits_slong_p($to)) {
                $to   = Math::GMPz::Rmpz_get_ui($to);
                $from = Math::GMPz::Rmpz_get_ui($from) if (ref($from) eq 'Math::GMPz');
            }

            my @list;

            if (FAST_MODE and HAS_PRIME_UTIL and !ref($to)) {
                Math::Prime::Util::forcomposites(sub { push @list, $_ }, $from, $to);
                return \@list;
            }

            for (my $k = $from ; $k <= $to ; ++$k) {
                if (!_is_prob_prime($k)) {
                    push @list, $k;
                }
            }

            \@list;
        }
    );
}

*each_composite = \&composites_each;

sub primes {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        $x = _any2mpz($$x) // return _array();
        $y = _any2mpz($$y) // return _array();
    }
    else {
        $x = _any2mpz($$x) // return _array();
        $y = $x;
        $x = 2;
    }

    if ($x > $y) {
        return _array();
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($y)) {
        if (ref($x)) {
            $x = (Math::GMPz::Rmpz_sgn($x) <= 0) ? 2 : Math::GMPz::Rmpz_get_ui($x);
        }
        $y = Math::GMPz::Rmpz_get_ui($y);
        return _array([map { bless \$_ } @{Math::Prime::Util::primes($x, $y)}]);
    }

    $x = (_big2uistr($x) // 0) if ref($x);
    $y = (_big2uistr($y) // 0);

    Math::Prime::Util::GMP::subint($y, $x) < ULONG_MAX
      or return undef;    # too large range

    _array([map { ($_ < ULONG_MAX) ? (bless \$_) : _set_int($_) } @{_primes($x, $y)}]);
}

sub prime_cluster {
    my ($lo, $hi, @diffs) = @_;

    ref($lo) eq __PACKAGE__ or _valid(\$lo);
    ref($hi) eq __PACKAGE__ or _valid(\$hi);
    _valid(\(@diffs));

    $lo = _big2uistr($$lo) // return undef;
    $hi = _big2uistr($$hi) // return undef;

    @diffs = map { _big2uistr($$_) // return undef } @diffs;

    my @primes = map { _set_int($_) } (
                                       HAS_PRIME_UTIL
                                       ? Math::Prime::Util::sieve_prime_cluster($lo, $hi, @diffs)
                                       : Math::Prime::Util::GMP::sieve_prime_cluster($lo, $hi, @diffs)
                                      );

    _array(\@primes);
}

*sieve_prime_cluster = \&prime_cluster;

sub _combine_crt {
    my ($arr_ref, $M, $p, $S_p_ref) = @_;

    my $Minv =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::invmod($M % $p, $p)
      : Math::Prime::Util::GMP::invmod($M % $p, $p);

    my @res;
    foreach my $r (@$arr_ref) {
        my $r_mod_p = $r % $p;
        foreach my $s (@$S_p_ref) {

            # Solve r + k*M ≡ s (mod p) -> k ≡ (s - r) * Minv (mod p)
            push @res, (((($s - $r_mod_p) % $p) * $Minv) % $p) * $M + $r;
        }
    }

    return \@res;
}

sub _remainders_mod_p {
    my ($p, $terms, $alphas) = @_;

    my @bad;    # bad[m] = 1 means m is forbidden modulo p

    foreach my $i (0 .. $#$terms) {

        my $m = $terms->[$i];
        my $k = $alphas->[$i];

        if (ref($m) or ref($k)) {
            $m = Math::Prime::Util::GMP::modint($m, $p);
            $k = Math::Prime::Util::GMP::modint($k, $p);
        }
        else {
            $m %= $p;
            $k %= $p;
        }

        if ($m == 0) {

            # Term is constant mod p
            if ($k == 0) {

                # Always 0 mod p -> no admissible residue exists
                return ();
            }
            next;    # This term forbids no residue for this p
        }

        # Forbid the unique residue m ≡ -k * m^{-1} (mod p)
        my $m_inv =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::invmod($m, $p)
          : Math::Prime::Util::GMP::invmod($m, $p);
        my $m_forbid = (-$k * $m_inv) % $p;
        $bad[$m_forbid] = 1;
    }

    my @S_p;
    foreach my $m (0 .. $p - 1) {
        push(@S_p, $m) if !$bad[$m];
    }
    return @S_p;
}

sub _remainders_for_primes {
    my ($primes) = @_;

    my $residues = [0];
    my $M        = 1;

    foreach my $pair (@$primes) {
        my ($p, $rems) = @$pair;
        $residues = _combine_crt($residues, $M, $p, $rems);
        $M *= $p;
    }

    [sort { $a <=> $b } @$residues];
}

sub _deltas {
    my ($integers) = @_;

    my @deltas;
    my $prev = 0;

    foreach my $n (@$integers) {
        push @deltas, $n - $prev;
        $prev = $n;
    }

    CORE::shift(@deltas);
    return \@deltas;
}

sub _select_optimal_primes {
    my ($A, $B, $terms, $alphas) = @_;

    my $range = $B - $A + 1;

    if ($range <= 0) {
        return;
    }

    my $target_modulus = (
                          1 + (
                               HAS_PRIME_UTIL
                               ? Math::Prime::Util::rootint($range, 5)
                               : Math::Prime::Util::GMP::rootint($range, 5)
                              )
                         )**4;

    my $M = 1;
    my @primes;

    for (my $p = 2 ; $M <= $target_modulus ; $p = _next_prime($p)) {
        my @S_p = _remainders_mod_p($p, $terms, $alphas);

        if (scalar(@S_p) == $p) {
            next;    # skip trivial primes
        }

        CORE::push(@primes, [$p, \@S_p]);
        $M *= $p;
    }

    return ($M, \@primes);
}

sub _linear_forms_primes_base {
    my ($A, $B, $pairs, $iter) = @_;

    # TODO: add support for $iter = 1, returning a Sidef::Object::Enumerator iterator

    ref($A) eq __PACKAGE__ or _valid(\$A);
    ref($B) eq __PACKAGE__ or _valid(\$B);

    my @terms  = map { $_->[0] } @$pairs;
    my @alphas = map { $_->[1] } @$pairs;

    _valid(\(@terms));
    _valid(\(@alphas));

    $A = _any2mpz(_big2uistr($$A) // return _array());
    $B = _any2mpz(_big2uistr($$B) // return _array());

    @terms  = map { _any2mpz($_) } map { _big2uistr($$_) // return undef } @terms;
    @alphas = map { _any2mpz($_) } map { _big2istr($$_)  // return undef } @alphas;

    if (Math::GMPz::Rmpz_cmp($A, $B) > 0 or !@terms or scalar(@alphas) != scalar(@terms)) {
        return _array();
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    # Check to see if we can do the computation using native ints (would be faster)
    my $native_int = 1;

    foreach my $i (0 .. $#terms) {

        if (!Math::GMPz::Rmpz_fits_ulong_p($terms[$i])) {
            $native_int = 0;
            last;
        }

        if (!Math::GMPz::Rmpz_fits_slong_p($alphas[$i])) {
            $native_int = 0;
            last;
        }

        Math::GMPz::Rmpz_mul($t, $B, $terms[$i]);
        if (!Math::GMPz::Rmpz_fits_ulong_p($t)) {
            $native_int = 0;
            last;
        }

        Math::GMPz::Rmpz_add($t, $t, $alphas[$i]);
        if (!Math::GMPz::Rmpz_fits_ulong_p($t)) {
            $native_int = 0;
            last;
        }
    }

    # $native_int = 0;

    if ($native_int) {
        @terms  = map { Math::GMPz::Rmpz_get_ui($_) } @terms;
        @alphas = map { Math::GMPz::Rmpz_get_si($_) } @alphas;
        $A      = Math::GMPz::Rmpz_get_ui($A);
        $B      = Math::GMPz::Rmpz_get_ui($B);
    }

    # Compute the list of primes, based on the size of the range (adaptive strategy)
    my ($M, $primes) = _select_optimal_primes($A, $B, \@terms, \@alphas);

    my $key = do {
        local $" = " ";
        "@terms | @alphas | " . join(' ', map { $_->[0] } @$primes);
    };

    state $cache = {};

    my $r = undef;
    if (exists $cache->{$key}) {
        $r = $cache->{$key};
    }
    else {
        $cache         = {};                                # clear cache
        $r             = _remainders_for_primes($primes);
        $cache->{$key} = $r;
    }

    if (!@$r) {
        return _array();
    }

    my @d = @{_deltas($r)};

    while (@d and $d[0] == 0) {
        CORE::shift(@d);
    }

    push @d, Math::Prime::Util::GMP::subint(Math::Prime::Util::GMP::addint($r->[0], $M), $r->[-1]);

    my $compute_small_values = 0;
    my $small_values_limit   = ($B < 500) ? $B : 500;
    my $original_A           = undef;

    if ($A <= $small_values_limit) {
        $original_A           = $A;
        $A                    = $small_values_limit + 1;
        $compute_small_values = 1;

        if (!$native_int) {
            $A = Math::GMPz::Rmpz_init_set_ui($A) if !ref($A);
        }
    }

    my $m     = Math::GMPz::Rmpz_init_set_str("$r->[0]", 10);
    my $d_len = scalar(@d);

    my $d_sum = Math::Prime::Util::GMP::vecsum(@d);
    my $times = Math::Prime::Util::GMP::divint($A, $d_sum);

    if ($times ne '0') {
        state $u = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_str($t, $d_sum, 10);
        Math::GMPz::Rmpz_set_str($u, $times, 10);
        Math::GMPz::Rmpz_mul($t, $t, $u);
        Math::GMPz::Rmpz_add($m, $m, $t);
    }

    my $j     = 0;
    my $ok    = 1;
    my @range = (0 .. $#terms);

    my @arr;

    if ($native_int) {

        $m = Math::GMPz::Rmpz_get_ui($m);

        while ($m < $A) {
            $m += $d[$j++ % $d_len];
        }

        if ($compute_small_values) {
            foreach my $k ($original_A .. $small_values_limit) {
                $ok = 1;

                foreach my $i (@range) {
                    if (
                        !(
                           HAS_PRIME_UTIL
                           ? Math::Prime::Util::is_prime($terms[$i] * $k + $alphas[$i])
                           : Math::Prime::Util::GMP::is_prime($terms[$i] * $k + $alphas[$i])
                         )
                      ) {
                        $ok = 0;
                        last;
                    }
                }

                if ($ok) {
                    push @arr, _set_int($k);
                }
            }
        }

        while ($m <= $B) {

            $ok = 1;

            foreach my $i (@range) {
                if (
                    !(
                       HAS_PRIME_UTIL
                       ? Math::Prime::Util::is_prime($terms[$i] * $m + $alphas[$i])
                       : Math::Prime::Util::GMP::is_prime($terms[$i] * $m + $alphas[$i])
                     )
                  ) {
                    $ok = 0;
                    last;
                }
            }

            if ($ok) {
                my $r = $m;
                push @arr, bless \$r;
            }

            $m += $d[$j++ % $d_len];
        }

        return _array(\@arr);
    }

    if ($compute_small_values) {
        foreach my $v ($original_A .. $small_values_limit) {

            $ok = 1;
            foreach my $i (@range) {

                my $k     = $terms[$i];
                my $alpha = $alphas[$i];

                Math::GMPz::Rmpz_mul_ui($t, $k, $v);
                Math::GMPz::Rmpz_add($t, $t, $alpha);

                if (!Math::GMPz::Rmpz_probab_prime_p($t, 23) or Math::GMPz::Rmpz_sgn($t) <= 0) {
                    $ok = 0;
                    last;
                }
            }

            if ($ok) {
                push @arr, _set_int($v);
            }
        }
    }

    while (Math::GMPz::Rmpz_cmp($m, $A) < 0) {
        Math::GMPz::Rmpz_add_ui($m, $m, $d[$j++ % $d_len]);
    }

    while (Math::GMPz::Rmpz_cmp($m, $B) <= 0) {

        $ok = 1;
        foreach my $i (@range) {

            my $k     = $terms[$i];
            my $alpha = $alphas[$i];

            Math::GMPz::Rmpz_mul($t, $k, $m);
            Math::GMPz::Rmpz_add($t, $t, $alpha);

            if (!Math::GMPz::Rmpz_probab_prime_p($t, 23) or Math::GMPz::Rmpz_sgn($t) <= 0) {
                $ok = 0;
                last;
            }
        }

        if ($ok) {
            push @arr, _set_int($m);
        }

        Math::GMPz::Rmpz_add_ui($m, $m, $d[$j++ % $d_len]);
    }

    _array(\@arr);
}

sub linear_forms_primes {
    my ($A, $B, @pairs) = @_;
    _linear_forms_primes_base($A, $B, \@pairs);
}

sub composites {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = 4;
    }

    if (ref($from) and Math::GMPz::Rmpz_cmp_ui($from, 3) <= 0) {
        $from = 4;
    }

    if (ref($to) and Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = 0;
    }

    if ($from > $to) {
        return _array();
    }

    if (_fits_ulong($to)) {
        $to   = _get_ulong($to);
        $from = Math::GMPz::Rmpz_get_ui($from) if (ref($from) eq 'Math::GMPz');
    }

    my @list;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($to)) {
        Math::Prime::Util::forcomposites(
            sub {
                push(@list, bless(\(my $o = $_)));
            },
            $from,
            $to
        );
    }
    else {
        for (my $k = $from ; $k - 1 < $to ; ++$k) {
            if (!_is_prob_prime($k)) {
                push(@list, ref($k) ? _set_int($k) : (bless(\(my $o = $k))));
            }
        }
    }

    _array(\@list);
}

sub smooth_numbers {
    my ($n, $primes, $block) = @_;

    my @primes = @$primes;
    @primes || return _array();

    _valid(\(@primes));
    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_sgn($n) > 0
      or return _array();

    @primes = map { ref($$_) ? (_fits_ulong($$_) ? _get_ulong($$_) : _any2mpz($$_)) : $$_ } @primes;

    # Optimization when n is a native integer
    if (Math::GMPz::Rmpz_cmp_ui($n, ULONG_MAX) < 0) {

        $n = Math::GMPz::Rmpz_get_ui($n);

        my @h = (ONE);
        foreach my $p (@primes) {
            my $p_obj = _set_int($p);
            foreach my $k (@h) {
                my $t = $$k * $p;
                if ($t <= $n and $t < ULONG_MAX) {
                    my $t_obj = bless \$t;
                    if (defined($block) ? $block->($t_obj, $p_obj) : 1) {
                        push @h, $t_obj;
                    }
                }
            }
        }

        @h = sort { $$a <=> $$b } @h;
        return _array(\@h);
    }

    my @h = (ONE);

    foreach my $p (@primes) {
        my $p_obj = _set_int($p);
        foreach my $k (@h) {
            my $t = __mul__($p, $$k);
            if (__cmp__($t, $n) <= 0) {
                my $t_obj = bless \$t;
                if (defined($block) ? $block->($t_obj, $p_obj) : 1) {
                    push @h, $t_obj;
                }
            }
        }
    }

    @h = sort { $$a <=> $$b } @h;
    return _array(\@h);
}

sub next_primes {
    my ($n, $start) = @_;

    $start = defined($start) ? do { ref($start) eq __PACKAGE__ or _valid(\$start); _big2uistr($$start) // 0 } : 2;
    $n     = _any2ui($$n) // return _array();

    my @primes;

    if (HAS_PRIME_UTIL and $start < (ULONG_MAX >> 1)) {
        for (my $it = Math::Prime::Util::prime_iterator($start) ; $n > 0 ; --$n) {
            my $p = $it->();
            push @primes, bless \$p;
        }
        return _array(\@primes);
    }

    if (_is_prob_prime($start)) {
        ## ok
    }
    else {
        $start = _next_prime($start);
    }

    for (my $p = $start ; $n > 0 ; --$n, ($p = _next_prime($p))) {
        push @primes, _set_int($p);
    }

    _array(\@primes);
}

sub next_composites {
    my ($n, $start) = @_;

    $n = _any2ui($$n) // return _array();

    if (defined($start)) {
        ref($start) eq __PACKAGE__ or _valid(\$start);
        $start = $start->dec->next_composite;
    }
    else {
        $start = _set_int(4);
    }

    my @composites;

    for (my $c = $start ; $n > 0 ; --$n, ($c = $c->next_composite)) {
        push @composites, $c;
    }

    _array(\@composites);
}

sub prev_primes {
    my ($n, $start) = @_;

    $n = _any2ui($$n) // return _array();

    ref($start) eq __PACKAGE__ or _valid(\$start);
    $start = $start->inc->prev_prime;

    my @primes;

    for (my $c = $start ; $n > 0 ; --$n, ($c = $c->prev_prime)) {
        last if $c->is_nan;
        push @primes, $c;
    }

    _array(\@primes);
}

sub prev_composites {
    my ($n, $start) = @_;

    $n = _any2ui($$n) // return _array();

    ref($start) eq __PACKAGE__ or _valid(\$start);
    $start = $start->inc->prev_composite;

    my @composites;

    for (my $c = $start ; $n > 0 ; --$n, ($c = $c->prev_composite)) {
        last if $c->is_nan;
        push @composites, $c;
    }

    _array(\@composites);
}

sub pn_primes {
    my ($x, $y) = @_;

    if (defined($y)) {
        ref($y) eq __PACKAGE__ or _valid(\$y);
        return $x->nth_prime->primes($y->nth_prime);
    }

    $x->nth_prime->primes;
}

sub _prime_sum_mpz {
    my ($n, $k) = @_;

    # Simple implementation of the prime-summation function:
    #   Sum_{p prime <= n} p^k, for fixed k >= 0.

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) <= 0) {
        return ZERO;
    }

    my $n_str = Math::GMPz::Rmpz_get_str($n, 10);
    my $r     = Math::Prime::Util::GMP::sqrtint($n_str);

    goto &nan if ($r > ULONG_MAX);

    state $t  = Math::GMPz::Rmpz_init_nobless();
    state $u  = Math::GMPz::Rmpz_init_nobless();
    state $p2 = Math::GMPz::Rmpz_init_nobless();
    state $ip = Math::GMPz::Rmpz_init_nobless();

    my @S_small = (0);
    my @S_large = (0);

    for my $i (1 .. $r) {
        my $q = Math::Prime::Util::GMP::divint($n_str, $i);
        push @S_large, Math::GMPz::Rmpz_init_set_str(Math::Prime::Util::GMP::powersum($q, $k), 10);
        push @S_small, Math::GMPz::Rmpz_init_set_str(Math::Prime::Util::GMP::powersum($i, $k), 10);
    }

    my ($target, $max_i);
    foreach my $p (@{_primes(2, $r)}) {

        Math::GMPz::Rmpz_set_ui($p2, $p);
        Math::GMPz::Rmpz_mul($p2, $p2, $p2);

        my $cp = $S_small[$p - 1];
        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
        Math::GMPz::Rmpz_tdiv_q($u, $n, $p2);

        $max_i = (Math::GMPz::Rmpz_cmp_ui($u, $r) < 0) ? Math::GMPz::Rmpz_get_ui($u) : $r;

        for my $i (1 .. $max_i) {
            Math::GMPz::Rmpz_set_ui($ip, $i);
            Math::GMPz::Rmpz_mul_ui($ip, $ip, $p);

            if (Math::GMPz::Rmpz_cmp_ui($ip, $r) <= 0) {
                $target = $S_large[$i * $p];
            }
            else {
                Math::GMPz::Rmpz_tdiv_q($ip, $n, $ip);
                $target = $S_small[Math::GMPz::Rmpz_get_ui($ip)];
            }
            Math::GMPz::Rmpz_sub($u, $target, $cp);
            Math::GMPz::Rmpz_submul($S_large[$i], $u, $t);
        }

        if (Math::GMPz::Rmpz_cmp_ui($p2, $r) <= 0) {
            my $p2_ui = Math::GMPz::Rmpz_get_ui($p2);
            for (my $v = $r ; $v >= $p2_ui ; $v--) {
                Math::GMPz::Rmpz_sub($u, $S_small[CORE::int($v / $p)], $cp);
                Math::GMPz::Rmpz_submul($S_small[$v], $u, $t);
            }
        }
    }

    _set_int($S_large[1] - 1);
}

sub _prime_sum_native {
    my ($n, $k) = @_;

    # Simple implementation of the prime-summation function:
    #   Sum_{p prime <= n} p^k, for fixed k >= 0.

    if ($n <= 1) {
        return ZERO;
    }

    my $r = Math::Prime::Util::GMP::sqrtint($n);

    goto &nan if ($r > ULONG_MAX);

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @S_small = (0);
    my @S_large = (0);

    for my $i (1 .. $r) {
        push @S_large, Math::GMPz::Rmpz_init_set_str(Math::Prime::Util::GMP::powersum(CORE::int($n / $i), $k), 10);
        push @S_small, Math::GMPz::Rmpz_init_set_str(Math::Prime::Util::GMP::powersum($i,                 $k), 10);
    }

    my ($target, $max_i, $p2, $ip, $cp);
    foreach my $p (@{_primes(2, $r)}) {
        $p2 = $p * $p;
        $cp = $S_small[$p - 1];
        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);

        $max_i = CORE::int($n / $p2);
        $max_i = $r if ($max_i > $r);

        for my $i (1 .. $max_i) {
            $ip     = $i * $p;
            $target = ($ip <= $r) ? $S_large[$ip] : $S_small[CORE::int($n / $ip)];
            Math::GMPz::Rmpz_sub($u, $target, $cp);
            Math::GMPz::Rmpz_submul($S_large[$i], $u, $t);
        }

        if ($p2 <= $r) {
            for (my $v = $r ; $v >= $p2 ; $v--) {
                Math::GMPz::Rmpz_sub($u, $S_small[CORE::int($v / $p)], $cp);
                Math::GMPz::Rmpz_submul($S_small[$v], $u, $t);
            }
        }
    }

    _set_int($S_large[1] - 1);
}

sub primes_sum {
    my ($from, $to, $k) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _big2istr($$from) // return ZERO;
        $from = 2 if $from < 2;
        $to   = _big2uistr($$to) // return ZERO;
    }
    else {
        $to   = _big2uistr($$from) // return ZERO;
        $from = 2;
    }

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
    }
    else {
        $k = 1;
    }

    if ($from > $to) {
        return ZERO;
    }

    if ($k == 0) {
        return _set_int($from)->prime_count(_set_int($to));
    }

    state $table_len = _next_prime(HAS_PRIME_UTIL ? 1e5 : 1e6);

    state $prime_sum_table = do {
        my @prime_sums;

        my $k   = 0;
        my $sum = 0;

        foreach my $p (@{_primes(2, $table_len)}) {
            splice(@prime_sums, $k, $p - $k + 1, ($sum) x ($p - $k + 1));
            $k = $p;
            $sum += $p;
        }

        \@prime_sums;
    };

    if ($k == 1 and $from eq '2') {
        if ($to < $table_len) {
            return _set_int($prime_sum_table->[$to]);
        }
    }

    if ($k == 1 and $from eq '2') {

        if ($to >= PRIMESUM_MIN and $to < 1e30 and $USE_PRIMESUM) {
            my $sum = `primesum $to`;

            if ($? == 0 and defined($sum)) {
                chomp $sum;
                if ($sum) {    # make sure the sum is not zero
                    return _set_int($sum);
                }
            }
        }
    }

    if ($k == 1 and $from ne '2' and $USE_PRIMESUM) {
        my $diff = Math::Prime::Util::GMP::subint($to, $from);

        if ($diff >= PRIMESUM_MIN and $diff < 1e30) {

            my $y_sum = `primesum $to`;

            if ($? == 0 and defined($y_sum)) {
                chomp $y_sum;
                my $x_sum = _set_int(Math::Prime::Util::GMP::subint($from, 1))->prime_sum;
                return _set_int(Math::Prime::Util::GMP::subint($y_sum, $x_sum));
            }
        }
    }

    if (HAS_PRIME_UTIL and $k == 1) {
        my $r = Math::Prime::Util::sum_primes($from, $to);
        return _set_int("$r");
    }

    if ($from > 2) {

        if ($from / $to >= 0.999) {
            return _set_int(Math::Prime::Util::GMP::vecsum(map { Math::Prime::Util::GMP::powint($_, $k) } @{_primes($from, $to)}));
        }

        return ((TWO)->primes_sum(_set_int($to), _set_int($k))->sub((TWO)->primes_sum(_set_int($from)->dec, _set_int($k))));
    }

    if (FAST_MODE and $to < ULONG_MAX) {
        return _prime_sum_native($to, $k);
    }

    my $n = _any2mpz($to) // goto &nan;
    _prime_sum_mpz($n, $k);
}

*prime_sum  = \&primes_sum;
*sum_primes = \&primes_sum;

sub prev_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        $n > 2 or goto &nan;
        return
          _set_int(
                   (
                    HAS_PRIME_UTIL
                    ? Math::Prime::Util::prev_prime($n)
                    : Math::Prime::Util::GMP::prev_prime($n)
                   )
                     || goto &nan
                  );
    }

    _set_int(Math::Prime::Util::GMP::prev_prime(_big2uistr($n) // goto &nan) || goto &nan);
}

sub next_prime {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n) and $n >= 0) {
        return
          _set_int(
                   HAS_PRIME_UTIL
                   ? Math::Prime::Util::next_prime($n)
                   : Math::Prime::Util::GMP::next_prime($n)
                  );
    }

    _set_int(Math::Prime::Util::GMP::next_prime(_big2uistr($n) // goto &nan));
}

sub next_twin_prime {
    my ($n) = @_;
    _set_int(Math::Prime::Util::GMP::next_twin_prime(_big2uistr($$n) // goto &nan) || goto &nan);
}

sub twin_primes {
    my ($lo, $hi) = @_;

    if (!defined($hi)) {
        $hi = $lo;
        $lo = THREE;
    }

    $lo->prime_cluster($hi, TWO);
}

sub is_twin_prime {
    my ($n) = @_;
    $n->add(TWO)->all_prime($n);
}

sub is_sexy_prime {
    my ($n) = @_;
    state $SIX = _set_int(6);
    $n->add($SIX)->all_prime($n);
}

sub is_sophie_germain {
    my ($n) = @_;
    $n->mul(TWO)->inc->all_prime($n);
}

*is_sophie_germain_prime = \&is_sophie_germain;

sub is_balanced_prime {
    my ($n, $order) = @_;

    $order //= ONE;
    $order = _any2ui($$order) // return $FALSE;

    $n->is_prime || return $FALSE;
    $order       || return $TRUE;

    if ($order > 1) {
        my $order_obj = bless \$order;

        my $prev = $order_obj->prev_primes($n->dec);
        my $next = $order_obj->next_primes($n->inc);

        scalar(@$prev) == scalar(@$next) or return $FALSE;
        my $sum = Sidef::Types::Number::Number::sum(@$prev, @$next);
        return $sum->div(_set_int(2 * $order))->eq($n);
    }

    my $prev = $n->prev_prime;
    my $next = $n->next_prime;

    $n->sub($prev)->eq($next->sub($n));
}

sub prev_composite {
    my ($n) = @_;

    $n = $$n;

    # Optimization for native integers
    if (FAST_MODE and !ref($n)) {
        $n <= 4 and goto &nan;
        $n = $n - 1;
        return (bless \$n) if (($n & 1) == 0);
        --$n               if _is_prob_prime($n);
        return bless \$n;
    }

    $n = _any2mpz($n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 4) <= 0 and goto &nan;

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    if (Math::GMPz::Rmpz_even_p($r)) {
        return bless \$r;
    }

    if (_is_prob_prime($r)) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    bless \$r;
}

sub next_composite {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 3) <= 0 and return _set_int(4);

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        return (bless \$n) if (($n & 1) == 0);
        ++$n               if _is_prob_prime($n);
        return (bless \$n);
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    if (Math::GMPz::Rmpz_even_p($r)) {
        return bless \$r;
    }

    if (_is_prob_prime($r)) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    bless \$r;
}

sub next_prime_power {
    my ($n) = @_;

    $n = $$n;

    # Optimization for native integers
    if (FAST_MODE and !ref($n) and $n >= 0 and $n < (ULONG_MAX >> 1)) {
        ++$n;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power($n) : Math::Prime::Util::GMP::is_prime_power($n)) {
            ++$n;
        }
        return bless \$n;
    }

    $n = _any2mpz($n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) < 0 and goto &nan;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power($n) : Math::Prime::Util::GMP::is_prime_power($n)) {
            ++$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    until (Math::Prime::Util::GMP::is_prime_power(Math::GMPz::Rmpz_get_str($r, 10))) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    bless \$r;
}

sub prev_prime_power {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 2) <= 0 and goto &nan;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) - 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power($n) : Math::Prime::Util::GMP::is_prime_power($n)) {
            --$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    until (Math::Prime::Util::GMP::is_prime_power(Math::GMPz::Rmpz_get_str($r, 10))) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    bless \$r;
}

sub next_squarefree {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;

    my $sgn = Math::GMPz::Rmpz_sgn($n);
    $sgn < 0  and goto &nan;
    $sgn == 0 and return ONE;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($n) : Math::Prime::Util::GMP::moebius($n)) {
            ++$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    my $r_obj = bless \$r;

    until ($r_obj->is_squarefree) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    $r_obj;
}

sub prev_squarefree {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 1) <= 0 and goto &nan;

    # Optimization for native integers
    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) - 1;
        until (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($n) : Math::Prime::Util::GMP::moebius($n)) {
            --$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    my $r_obj = bless \$r;

    until ($r_obj->is_squarefree) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    $r_obj;
}

sub next_powerfree {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 2 or goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 2) {
        return $n->next_squarefree;
    }

    $n = _any2mpz($$n) // goto &nan;

    my $sgn = Math::GMPz::Rmpz_sgn($n);
    $sgn < 0  and goto &nan;
    $sgn == 0 and return ONE;

    # Optimization for native integers
    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_slong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) + 1;
        until (Math::Prime::Util::is_powerfree($n, $k)) {
            ++$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_add_ui($r, $n, 1);

    my $k_obj = bless \$k;
    my $r_obj = bless \$r;

    until ($r_obj->is_powerfree($k_obj)) {
        Math::GMPz::Rmpz_add_ui($r, $r, 1);
    }

    $r_obj;
}

sub next_cubefree {
    my ($n) = @_;
    $n->next_powerfree(THREE);
}

sub prev_powerfree {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 2 or goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 2) {
        return $n->prev_squarefree;
    }

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_cmp_ui($n, 1) <= 0 and goto &nan;

    # Optimization for native integers
    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n) - 1;
        until (Math::Prime::Util::is_powerfree($n, $k)) {
            --$n;
        }
        return bless \$n;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub_ui($r, $n, 1);

    my $k_obj = bless \$k;
    my $r_obj = bless \$r;

    until ($r_obj->is_powerfree($k_obj)) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }

    $r_obj;
}

sub prev_cubefree {
    my ($n) = @_;
    $n->prev_powerfree(THREE);
}

sub znorder {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    if (FAST_MODE and !ref($y) and !ref($x)) {
        my $r = (
                 HAS_PRIME_UTIL
                 ? Math::Prime::Util::znorder($x, $y)
                 : Math::Prime::Util::GMP::znorder($x, $y)
                ) // goto &nan;
        return bless \$r;
    }

    $x = _big2istr($x) // goto &nan;
    $y = _big2istr($y) // goto &nan;

    if (CORE::length($y) >= SPECIAL_FACTORS_MIN) {
        Math::Prime::Util::GMP::gcd($y, $x) eq '1' or goto &nan;
        $y = Math::Prime::Util::GMP::absint($y) if (substr($y, 0, 1) eq '-');
        $x = Math::Prime::Util::GMP::modint($x, $y);
        my @znorders;
        foreach my $pp (_factor_exp($y)) {
            my ($p, $e) = @$pp;
            if (HAS_PRIME_UTIL and $e == 1 and $p < ULONG_MAX) {
                push @znorders, Math::Prime::Util::znorder($x, $p);
            }
            else {
                push @znorders, Math::Prime::Util::GMP::znorder($x, (($e == 1) ? $p : Math::Prime::Util::GMP::powint($p, $e)));
            }
        }
        return _set_int(Math::Prime::Util::GMP::lcm(@znorders));
    }

    _set_int(Math::Prime::Util::GMP::znorder($x, $y) // goto &nan);
}

*multiplicative_order = \&znorder;

sub znprimroot {
    my ($n) = @_;
    _set_int(Math::Prime::Util::GMP::znprimroot(_big2uistr($$n) // (goto &nan)) // goto &nan);
}

sub _znlog_bsgs {    # Baby-step, Giant-step algorithm
    my ($A, $g, $p, $n) = @_;

    # Calculate m = ceil(sqrt(n))
    state $m = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sqrt($m, $n);
    Math::GMPz::Rmpz_add_ui($m, $m, 1);

    my $limit = 1_000_000;
    my $m_ui  = Math::GMPz::Rmpz_cmp_ui($m, $limit) <= 0 ? Math::GMPz::Rmpz_get_ui($m) : $limit;

    # Calculate inverse of g
    state $invg = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_invert($invg, $g, $p) || return undef;

    state $gm            = Math::GMPz::Rmpz_init_nobless();
    state $current_baby  = Math::GMPz::Rmpz_init_nobless();
    state $current_giant = Math::GMPz::Rmpz_init_nobless();

    for (my $max_m = 1 ; ; $max_m <<= 1) {    # TODO: improve this

        my %baby_steps;
        Math::GMPz::Rmpz_set_ui($current_baby, 1);

        # Phase 1: Baby Steps
        # Calculate g^j mod p for 0 <= j < m
        for my $j (0 .. $max_m - 1) {

            my $key = Math::GMPz::Rmpz_get_str($current_baby, 62);

            # Keep the smallest j for a given value to ensure minimal exponent
            $baby_steps{$key} = $j if !exists $baby_steps{$key};

            Math::GMPz::Rmpz_mul($current_baby, $current_baby, $g);
            Math::GMPz::Rmpz_mod($current_baby, $current_baby, $p);
        }

        # Phase 2: Giant Steps setup
        # Calculate gm = g^{-m} mod p
        Math::GMPz::Rmpz_powm_ui($gm, $invg, $max_m, $p);
        Math::GMPz::Rmpz_set($current_giant, $A);

        # Calculate a * (g^{-m})^i mod p for 0 <= i < m
        for my $i (0 .. $max_m - 1) {
            my $key = Math::GMPz::Rmpz_get_str($current_giant, 62);

            if (exists $baby_steps{$key}) {
                my $j = $baby_steps{$key};

                # Result = i * m + j
                my $result = Math::GMPz::Rmpz_init_set_ui($i);
                Math::GMPz::Rmpz_mul_ui($result, $result, $max_m);
                Math::GMPz::Rmpz_add_ui($result, $result, $j);
                Math::GMPz::Rmpz_mod($result, $result, $n);

                return $result;
            }

            Math::GMPz::Rmpz_mul($current_giant, $current_giant, $gm);
            Math::GMPz::Rmpz_mod($current_giant, $current_giant, $p);
        }

        last if $max_m >= $m_ui;
    }

    return undef;
}

# Pollard's rho for discrete logarithm in a group of prime order
# Returns x such that g^x = h (mod n) where g has order p (prime)
sub _znlog_pollard_rho {
    my ($g, $h, $p, $n, $max_tries) = @_;

    $max_tries //= 10;

    # Trivial cases
    if (Math::GMPz::Rmpz_cmp_ui($h, 1) == 0) {
        return Math::GMPz::Rmpz_init_set_ui(0);
    }
    if (Math::GMPz::Rmpz_cmp($g, $h) == 0) {
        return Math::GMPz::Rmpz_init_set_ui(1);
    }

    # For very small prime orders, use Baby-Step, Giant-Step algorithm
    if (Math::GMPz::Rmpz_cmp_ui($p, 100) <= 0) {

        my $res = _znlog_bsgs($h, $g, $n, $p);

        if (defined($res)) {
            return $res;
        }

        # Alternatively, brute-force search
        my $t = Math::GMPz::Rmpz_init_set_ui(1);
        for my $i (0 .. Math::GMPz::Rmpz_get_ui($p) - 1) {
            if (Math::GMPz::Rmpz_cmp($t, $h) == 0) {
                return Math::GMPz::Rmpz_init_set_ui($i);
            }
            Math::GMPz::Rmpz_mul($t, $t, $g);
            Math::GMPz::Rmpz_mod($t, $t, $n);
        }
        return undef;
    }

    state $rng = Math::GMPz::zgmp_randinit_default_nobless();

    state $tmp = Math::GMPz::Rmpz_init_nobless();
    state $a1  = Math::GMPz::Rmpz_init_nobless();
    state $b1  = Math::GMPz::Rmpz_init_nobless();
    state $x1  = Math::GMPz::Rmpz_init_nobless();
    state $a2  = Math::GMPz::Rmpz_init_nobless();
    state $b2  = Math::GMPz::Rmpz_init_nobless();
    state $x2  = Math::GMPz::Rmpz_init_nobless();

    state $da = Math::GMPz::Rmpz_init_nobless();
    state $db = Math::GMPz::Rmpz_init_nobless();

    state $invdb = Math::GMPz::Rmpz_init_nobless();

    foreach my $attempt (1 .. $max_tries) {

        # Random starting point (a,b) with X = g^a * h^b
        Math::GMPz::Rmpz_urandomm($a1, $b1, $rng, $p, 2);    # 0 <= a1 < p

        Math::GMPz::Rmpz_powm($x1,  $g, $a1, $n);            # g^a1 mod n
        Math::GMPz::Rmpz_powm($tmp, $h, $b1, $n);            # h^b1 mod n
        Math::GMPz::Rmpz_mul($x1, $x1, $tmp);
        Math::GMPz::Rmpz_mod($x1, $x1, $n);

        # Hare starts same as tortoise
        Math::GMPz::Rmpz_set($a2, $a1);
        Math::GMPz::Rmpz_set($b2, $b1);
        Math::GMPz::Rmpz_set($x2, $x1);

        while (1) {

            # Tortoise step (Inlined)
            my $r1 = Math::GMPz::Rmpz_mod_ui($tmp, $x1, 3);
            if ($r1 == 0) {
                Math::GMPz::Rmpz_add_ui($a1, $a1, 1);
                Math::GMPz::Rmpz_mul($x1, $x1, $g);
                Math::GMPz::Rmpz_mod($x1, $x1, $n);
            }
            elsif ($r1 == 1) {
                Math::GMPz::Rmpz_add_ui($b1, $b1, 1);
                Math::GMPz::Rmpz_mul($x1, $x1, $h);
                Math::GMPz::Rmpz_mod($x1, $x1, $n);
            }
            else {
                Math::GMPz::Rmpz_mul_2exp($a1, $a1, 1);
                Math::GMPz::Rmpz_mul_2exp($b1, $b1, 1);
                Math::GMPz::Rmpz_mod($a1, $a1, $p);
                Math::GMPz::Rmpz_mod($b1, $b1, $p);
                Math::GMPz::Rmpz_powm_ui($x1, $x1, 2, $n);
            }

            # Hare step (Inlined, two iterations)
            for (1 .. 2) {
                my $r2 = Math::GMPz::Rmpz_mod_ui($tmp, $x2, 3);
                if ($r2 == 0) {
                    Math::GMPz::Rmpz_add_ui($a2, $a2, 1);
                    Math::GMPz::Rmpz_mul($x2, $x2, $g);
                    Math::GMPz::Rmpz_mod($x2, $x2, $n);
                }
                elsif ($r2 == 1) {
                    Math::GMPz::Rmpz_add_ui($b2, $b2, 1);
                    Math::GMPz::Rmpz_mul($x2, $x2, $h);
                    Math::GMPz::Rmpz_mod($x2, $x2, $n);
                }
                else {
                    Math::GMPz::Rmpz_mul_2exp($a2, $a2, 1);
                    Math::GMPz::Rmpz_mul_2exp($b2, $b2, 1);
                    Math::GMPz::Rmpz_mod($a2, $a2, $p);
                    Math::GMPz::Rmpz_mod($b2, $b2, $p);
                    Math::GMPz::Rmpz_powm_ui($x2, $x2, 2, $n);
                }
            }

            if (Math::GMPz::Rmpz_cmp($x1, $x2) == 0) {

                # Collision: g^{a1} h^{b1} = g^{a2} h^{b2}
                Math::GMPz::Rmpz_sub($da, $a1, $a2);
                Math::GMPz::Rmpz_mod($da, $da, $p);

                Math::GMPz::Rmpz_sub($db, $b2, $b1);
                Math::GMPz::Rmpz_mod($db, $db, $p);

                if (Math::GMPz::Rmpz_sgn($db) == 0) {
                    last;    # degenerate case, restart
                }

                Math::GMPz::Rmpz_invert($invdb, $db, $p) || last;

                my $x = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($x, $da, $invdb);
                Math::GMPz::Rmpz_mod($x, $x, $p);

                # Verify
                Math::GMPz::Rmpz_powm($tmp, $g, $x, $n);
                if (Math::GMPz::Rmpz_cmp($tmp, $h) == 0) {
                    return $x;
                }
                last;    # verification failed, restart
            }
        }
    }
    return undef;
}

# Solve g^x = a (mod n) where g has order exactly p^e * r,
# and we want x modulo p^e.
sub _znlog_prime_power {
    my ($A, $g, $n, $p, $e, $full_order) = @_;

    my $L = $full_order;
    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_pow_ui($r, $p, $e);    # p^e
    Math::GMPz::Rmpz_tdiv_q($r, $L, $r);    # r = L / p^e

    state $g0 = Math::GMPz::Rmpz_init_nobless();
    state $a0 = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_powm($g0, $g, $r, $n);
    Math::GMPz::Rmpz_powm($a0, $A, $r, $n);

    my $x = Math::GMPz::Rmpz_init_set_ui(0);

    state $cur_g = Math::GMPz::Rmpz_init_nobless();
    state $cur_a = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set($cur_g, $g0);
    Math::GMPz::Rmpz_set($cur_a, $a0);

    state $f = Math::GMPz::Rmpz_init_nobless();    # multiplier for current digit
    Math::GMPz::Rmpz_set_ui($f, 1);

    state $tmp   = Math::GMPz::Rmpz_init_nobless();
    state $sub_g = Math::GMPz::Rmpz_init();
    state $sub_a = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_pow_ui($tmp, $p, $e - 1);
    Math::GMPz::Rmpz_powm($sub_g, $g0, $tmp, $n);

    foreach my $i (0 .. $e - 1) {

        Math::GMPz::Rmpz_pow_ui($tmp, $p, $e - $i - 1);    # p^(e-1-i)
        Math::GMPz::Rmpz_powm($sub_a, $cur_a, $tmp, $n);

        my $d = _znlog_pollard_rho($sub_g, $sub_a, $p, $n) // return undef;

        # x += d * f
        Math::GMPz::Rmpz_mul($tmp, $d, $f);
        Math::GMPz::Rmpz_add($x, $x, $tmp);

        # f *= p
        Math::GMPz::Rmpz_mul($f, $f, $p);

        # Remove the found part: cur_a = cur_a * inv(cur_g^d) mod n
        Math::GMPz::Rmpz_powm($tmp, $cur_g, $d, $n);
        Math::GMPz::Rmpz_invert($tmp, $tmp, $n) || return undef;
        Math::GMPz::Rmpz_mul($cur_a, $cur_a, $tmp);
        Math::GMPz::Rmpz_mod($cur_a, $cur_a, $n);

        # Next generator: cur_g = cur_g^p mod n
        Math::GMPz::Rmpz_powm($cur_g, $cur_g, $p, $n);
    }
    return $x;
}

# Solve g^x = a (mod n) where gcd(g, n) = 1, using Pohlig-Hellman over order of g.
# Suitable when n is a prime power (or when called per prime-power factor of n).
sub _znlog_coprime_prime_power {
    my ($A, $g, $n) = @_;

    # Pipe into PARI/GP for cryptographic-scale inputs
    # We bypass the system() overhead for small numbers
    if ($USE_PARI_GP && Math::GMPz::Rmpz_sizeinbase($n, 2) >= 20) {
        my $a_str = Math::GMPz::Rmpz_get_str($A, 10);
        my $g_str = Math::GMPz::Rmpz_get_str($g, 10);
        my $n_str = Math::GMPz::Rmpz_get_str($n, 10);

        # We use iferr() so PARI doesn't crash the pipeline if no solution exists.
        # znlog natively triggers the index calculus/NFS backend based on the Mod() sizes.
        my $code = "iferr(znlog(Mod($a_str, $n_str), Mod($g_str, $n_str)), E, \"\")";

        if (my $res = _execute_pari_gp($code)) {
            if ($res =~ /^-?[0-9]+\z/) {
                return _str2obj($res);
            }

            # If PARI returns empty, the discrete logarithm does not exists
            return undef;
        }
    }

    my $order = _any2mpz((Math::Prime::Util::GMP::znorder($g, $n) // return undef), 4);

    state $tmp   = Math::GMPz::Rmpz_init_nobless();
    state $p_mpz = Math::GMPz::Rmpz_init_nobless();

    # Quick necessary condition: a must lie in the subgroup generated by g
    Math::GMPz::Rmpz_powm($tmp, $A, $order, $n);
    if (Math::GMPz::Rmpz_cmp_ui($tmp, 1) != 0) {
        return undef;
    }

    # Trivial case
    if (Math::GMPz::Rmpz_cmp_ui($order, 1) == 0) {
        if (Math::GMPz::Rmpz_cmp_ui($A, 1) == 0) {
            return 0;
        }
        return undef;
    }

    # Factor the order into prime powers and solve for each
    my @factors  = _factor_exp($order);
    my @residues = ();

    foreach my $pp (@factors) {
        my ($p, $e) = @$pp;
        Math::GMPz::Rmpz_set_str($p_mpz, $p, 10);
        my $x = _znlog_prime_power($A, $g, $n, $p_mpz, $e, $order) // return undef;
        push @residues, [$x, Math::Prime::Util::GMP::powint($p, $e)];
    }

    # Combine via CRT
    my $x = Math::Prime::Util::GMP::chinese(@residues) // return undef;

    # Verify
    Math::GMPz::Rmpz_set_str($tmp, $x, 10);
    Math::GMPz::Rmpz_powm($tmp, $g, $tmp, $n);
    if (Math::GMPz::Rmpz_cmp($tmp, $A) == 0) {
        return $x;
    }
    return undef;
}

sub _znlog_pohlig_hellman {
    my ($A, $g, $n, $order) = @_;

    my $tmp = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_gcd($tmp, $g, $n);

    # Handle non-coprime case: gcd(g, n) > 1
    if (Math::GMPz::Rmpz_cmp_ui($tmp, 1) != 0) {

        my $g_pow = Math::GMPz::Rmpz_init_set_ui(1);    # g^k mod n (original n), for direct equality check
        my $n_red = Math::GMPz::Rmpz_init_set($n);      # modulus being reduced
        my $a_red = Math::GMPz::Rmpz_init_set($A);      # target being reduced
        my $d_acc = Math::GMPz::Rmpz_init_set_ui(1);    # accumulated product: (g/D_1)*(g/D_2)*...*(g/D_k) mod n_red

        my $k = 0;
        while (1) {

            Math::GMPz::Rmpz_gcd($tmp, $g, $n_red);
            Math::GMPz::Rmpz_cmp_ui($tmp, 1) == 0 and last;

            # Check if g^k already equals a (mod n)
            if (Math::GMPz::Rmpz_cmp($g_pow, $A) == 0) {
                return $k;
            }

            Math::GMPz::Rmpz_divisible_p($a_red, $tmp) || return undef;

            Math::GMPz::Rmpz_div($n_red, $n_red, $tmp);
            Math::GMPz::Rmpz_div($a_red, $a_red, $tmp);

            Math::GMPz::Rmpz_div($tmp, $g, $tmp);
            Math::GMPz::Rmpz_mul($d_acc, $d_acc, $tmp);
            Math::GMPz::Rmpz_mul($g_pow, $g_pow, $g);
            Math::GMPz::Rmpz_mod($d_acc, $d_acc, $n_red);
            Math::GMPz::Rmpz_mod($g_pow, $g_pow, $n);

            ++$k;
        }

        # Final direct check after stripping
        if (Math::GMPz::Rmpz_cmp($g_pow, $A) == 0) {
            return $k;
        }

        # Phase 2: gcd(g, n_red) = 1 now; solve g^y = a_red * inv(d_acc) (mod n_red)
        Math::GMPz::Rmpz_invert($tmp, $d_acc, $n_red) || return undef;
        Math::GMPz::Rmpz_mul($tmp, $tmp, $a_red);

        my $new_a = Math::GMPz::Rmpz_init();
        my $new_g = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_mod($new_a, $tmp, $n_red);
        Math::GMPz::Rmpz_mod($new_g, $g,   $n_red);

        my $y = __SUB__->($new_a, $new_g, $n_red) // return undef;

        return ($y + $k);
    }

    # Coprime case: gcd(g, n) = 1

    # Factor n into prime powers
    my @n_factors = _factor_exp($n);

    # Composite n: solve g^x = a (mod p^e) for each prime-power factor, then CRT
    my @residues = ();

    my $pe  = Math::GMPz::Rmpz_init();
    my $g_i = Math::GMPz::Rmpz_init();
    my $a_i = Math::GMPz::Rmpz_init();

    foreach my $pp (@n_factors) {
        my ($p, $e) = @$pp;

        Math::GMPz::Rmpz_set_str($pe, $p, 10);
        Math::GMPz::Rmpz_pow_ui($pe, $pe, $e);
        Math::GMPz::Rmpz_mod($g_i, $g, $pe);
        Math::GMPz::Rmpz_mod($a_i, $A, $pe);

        my $r     = _znlog_coprime_prime_power($a_i, $g_i, $pe) // return undef;
        my $ord_i = Math::Prime::Util::GMP::znorder($g_i, $pe)  // return undef;

        push @residues, [$r, $ord_i];
    }

    my $x = _any2mpz(Math::Prime::Util::GMP::chinese(@residues) // return undef);

    # Verify the result
    Math::GMPz::Rmpz_powm($tmp, $g, $x, $n);
    if (Math::GMPz::Rmpz_cmp($tmp, $A) == 0) {
        return $x;
    }

    return undef;
}

sub znlog {
    my ($A, $g, $n) = @_;

    ref($g) eq __PACKAGE__ or _valid(\$g);
    ref($n) eq __PACKAGE__ or _valid(\$n);

    $A = _any2mpz($$A) // goto &nan;
    $g = _any2mpz($$g) // goto &nan;
    $n = _any2mpz($$n) // goto &nan;

    my $sgn = Math::GMPz::Rmpz_sgn($n) || goto &nan;

    if ($sgn < 0) {
        $n = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_abs($n, $n);
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
        return ZERO;
    }

    $A = Math::GMPz::Rmpz_init_set($A);    # copy
    $g = Math::GMPz::Rmpz_init_set($g);    # copy

    Math::GMPz::Rmpz_mod($A, $A, $n);
    Math::GMPz::Rmpz_mod($g, $g, $n);

    if (   Math::GMPz::Rmpz_cmp_ui($A, 1) == 0
        or Math::GMPz::Rmpz_cmp_ui($g, 0) == 0) {
        return ZERO;
    }

    _set_int(_znlog_pohlig_hellman($A, $g, $n) // goto &nan);
}

*discrete_log = \&znlog;

sub rad {    # A007947
    my ($n) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {    # native integer

        $n < 0 and goto &nan;

        if (HAS_PRIME_UTIL) {
            $n || return ZERO;
            my $r = Math::Prime::Util::vecprod(map { $_->[0] } Math::Prime::Util::factor_exp($n));
            return bless \$r;
        }
    }

    $n || return ZERO;
    _set_int(Math::Prime::Util::GMP::vecprod(map { $_->[0] } _factor_exp($n)));
}

*squarefree_kernel = \&rad;

sub powerfree_kernel {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = p^(min(e, k-1))

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = _any2ui($$k) || goto &nan;
    $n = _big2uistr($$n) // goto &nan;

    $n || return ZERO;

    _set_int(
             Math::Prime::Util::GMP::vecprod(map { ($_->[1] == 1) ? $_->[0] : Math::Prime::Util::GMP::powint($_->[0], $_->[1]) }
                                             map { [$_->[0], List::Util::min($k - 1, $_->[1])] } _factor_exp($n))
            );
}

sub cubefree_kernel {
    (THREE)->powerfree_kernel($_[0]);
}

sub powerful_part {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e, k) = p^e, where e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = _any2ui($$k) || goto &nan;
    $n = _big2uistr($$n) // goto &nan;

    $n || return ZERO;

    _set_int(
             Math::Prime::Util::GMP::vecprod(map  { Math::Prime::Util::GMP::powint($_->[0], $_->[1]) }
                                             grep { $_->[1] >= $k } _factor_exp($n))
            );
}

sub squarefull_part {
    (TWO)->powerful_part($_[0]);
}

sub cubefull_part {
    (THREE)->powerful_part($_[0]);
}

sub powerfree_part {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e, k) = p^(e mod k)

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = _any2ui($$k) || goto &nan;
    $n = _big2uistr($$n) // goto &nan;

    $n || return ZERO;

    _set_int(
             Math::Prime::Util::GMP::vecprod(map { ($_->[1] == 1) ? $_->[0] : Math::Prime::Util::GMP::powint($_->[0], $_->[1]) }
                                             grep { $_->[1] } map { [$_->[0], $_->[1] % $k] } _factor_exp($n))
            );
}

sub core {    # A007913
    (TWO)->powerfree_part($_[0]);
}

*squarefree_part = \&core;

sub cubefree_part {    # A050985
    (THREE)->powerfree_part($_[0]);
}

sub power_part {
    my ($k, $n) = @_;
    ref($n) eq __PACKAGE__ or _valid(\$n);
    $n->is_zero && return ZERO;
    $n->div($k->powerfree_part($n));
}

sub square_part {
    (TWO)->power_part($_[0]);
}

sub cube_part {
    (THREE)->power_part($_[0]);
}

sub powerfree_part_sum {
    my ($k, $from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->powerfree_part_sum($to)->sub($k->powerfree_part_sum($from->dec));
    }

    ref($from) eq __PACKAGE__ or _valid(\$from);

    my $n = _any2mpz($$from) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return ZERO;

    return ZERO        if ($k == 0);
    return (bless \$n) if ($k == 1);

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $w = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_root($t, $n, $k);
    Math::GMPz::Rmpz_fits_ulong_p($t) || goto &nan;    # too large

    my $s   = Math::GMPz::Rmpz_get_ui($t);
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    if (HAS_PRIME_UTIL) {
        Math::Prime::Util::forfactored(
            sub {

                # u = faulhaber(floor(n/v^k), 1)
                Math::GMPz::Rmpz_ui_pow_ui($w, $_, $k);
                Math::GMPz::Rmpz_div($t, $n, $w);
                Math::GMPz::Rmpz_mul($u, $t, $t);
                Math::GMPz::Rmpz_add($u, $u, $t);
                Math::GMPz::Rmpz_div_2exp($u, $u, 1);

                Math::GMPz::Rmpz_set_ui($w, 1);

                my $prev = 1;
                foreach my $p (@_) {
                    next if ($p == $prev);
                    Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
                    Math::GMPz::Rmpz_ui_sub($t, 1, $t);
                    Math::GMPz::Rmpz_mul($w, $w, $t);
                    $prev = $p;
                }

                Math::GMPz::Rmpz_mul($u, $u, $w);
                Math::GMPz::Rmpz_add($sum, $sum, $u);
            },
            $s
        );
    }
    else {
        my $m;
        for (my $v = 1 ; $v <= $s ; ++$v) {

            # u = faulhaber(floor(n/v^k), 1)
            Math::GMPz::Rmpz_ui_pow_ui($w, $v, $k);
            Math::GMPz::Rmpz_div($t, $n, $w);
            Math::GMPz::Rmpz_mul($u, $t, $t);
            Math::GMPz::Rmpz_add($u, $u, $t);
            Math::GMPz::Rmpz_div_2exp($u, $u, 1);

            Math::GMPz::Rmpz_set_ui($w, 1);

            my $prev = 1;
            foreach my $p (_factor($v)) {
                next if ($p == $prev);
                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
                Math::GMPz::Rmpz_ui_sub($t, 1, $t);
                Math::GMPz::Rmpz_mul($w, $w, $t);
                $prev = $p;
            }

            Math::GMPz::Rmpz_mul($u, $u, $w);
            Math::GMPz::Rmpz_add($sum, $sum, $u);
        }
    }

    return bless \$sum;
}

sub arithmetic_derivative {
    my ($x) = @_;

    my $deriv = sub {
        my ($n) = @_;

        # (-a)' = -(a')
        if (Math::GMPz::Rmpz_sgn($n) < 0) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_neg($t, $n);
            $t = __SUB__->($t);
            Math::GMPz::Rmpz_neg($t, $t);
            return $t;
        }

        my $u = Math::GMPz::Rmpz_init();
        my $d = Math::GMPz::Rmpz_init_set_ui(0);
        my $s = Math::GMPz::Rmpz_get_str($n, 10);

        return $d if ($s eq '0' or $s eq '1');

        foreach my $pk (_factor_exp($s)) {
            my ($p, $k) = @$pk;

            # a(n) = Sum_{p^k|n} (n*k)/p

            if (FAST_MODE and $p < ULONG_MAX) {
                Math::GMPz::Rmpz_divexact_ui($u, $n, $p);
            }
            else {
                Math::GMPz::Rmpz_set_str($u, "$p", 10);
                Math::GMPz::Rmpz_divexact($u, $n, $u);
            }

            Math::GMPz::Rmpz_addmul_ui($d, $u, $k);
        }

        return $d;
    };

    my $n = $$x;

    # (a/b)' = (a'b - b'a) / b^2
    if (ref($n) eq 'Math::GMPq') {

        my $t1 = Math::GMPz::Rmpz_init();
        my $t2 = Math::GMPz::Rmpz_init();

        Math::GMPq::Rmpq_get_num($t1, $n);    # a
        Math::GMPq::Rmpq_get_den($t2, $n);    # b

        my $d1 = $deriv->($t1);               # a'
        my $d2 = $deriv->($t2);               # b'

        Math::GMPz::Rmpz_mul($d1, $d1, $t2);  # d1 = a' * b
        Math::GMPz::Rmpz_mul($d2, $d2, $t1);  # d2 = b' * a
        Math::GMPz::Rmpz_mul($t2, $t2, $t2);  # t2 = b^2
        Math::GMPz::Rmpz_sub($d1, $d1, $d2);  # d1 = (a'b - b'a)

        # q = d1 / t2
        my $q = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set_num($q, $d1);
        Math::GMPq::Rmpq_set_den($q, $t2);
        Math::GMPq::Rmpq_canonicalize($q);
        return bless \$q;
    }

    bless \($deriv->(_any2mpz($n) // goto &nan));
}

*derivative = \&arithmetic_derivative;

sub logarithmic_derivative {
    my ($n) = @_;
    $n->arithmetic_derivative->div($n);
}

sub lpf {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n) and $n < 0xffffffff) {
        $n >= 0 or goto &nan;
        return bless \$n if ($n <= 1);

        $n % 2 == 0 and return TWO;
        $n % 3 == 0 and return THREE;

        my @f =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::factor($n)
          : Math::Prime::Util::GMP::factor($n);
        my $r = $f[0];
        return bless \$r;
    }

    $n = _any2mpz($n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) >= 0 or goto &nan;

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) <= 0) {
        return _set_int($n);
    }

    foreach my $p (2, 3, 5, 7) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $p)) {
            return _set_int($p);
        }
    }

    my $size = Math::GMPz::Rmpz_sizeinbase($n, 2);

    foreach my $j (2 .. 8) {

        my (undef, $f) = _primorial_trial_factor($n, 10**$j);

        if (defined($f)) {
            return _set_int($f);
        }

        last if (($j >= 5) && ($size <= 100));    # 30 digits
        last if (($j >= 6) && ($size <= 133));    # 40 digits
        last if (($j >= 7) && ($size <= 150));    # 45 digits
    }

    my @f = _factor($n);
    _set_int($f[0]);
}

sub lpf_sum {    # sum of lpf(k)
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->lpf_sum->sub($from->dec->lpf_sum);
    }

    my $n = $$from;

    if (ref($n)) {
        $n = _big2istr($n) // goto &nan;
    }

    if ($n < 0) {
        return ZERO;
    }

    my $s = Math::Prime::Util::GMP::sqrtint($n);

    if ($s > ULONG_MAX) {    # too large
        goto &nan;
    }

    if (HAS_PRIME_UTIL and ((INTSIZE >= 64) ? ($n <= 1e10) : ($n <= 1e5))) {

        my $pi  = 0;
        my $sum = 0;

        foreach my $p (@{Math::Prime::Util::primes($s)}) {
            $sum += $p * Math::Prime::Util::legendre_phi(CORE::int($n / $p), $pi++);
        }

        return _set_int($sum)->add(_set_int(_next_prime($s))->primes_sum(_set_int($n)));
    }

    my $pi  = 0;
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $p (@{_primes(2, $s)}) {
        my $d = Math::Prime::Util::GMP::divint($n, $p);
        my $r =
          (HAS_PRIME_UTIL and $d < ULONG_MAX)
          ? Math::Prime::Util::legendre_phi($d, $pi)
          : ${(bless \$p)->rough_count(_set_int($d))};
        my $w = Math::Prime::Util::GMP::mulint($r, $p);
        if (FAST_MODE and $w < ULONG_MAX) {
            Math::GMPz::Rmpz_add_ui($sum, $sum, $w);
        }
        else {
            state $t = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_set_str($t, "$w", 10);
            Math::GMPz::Rmpz_add($sum, $sum, $t);
        }
        ++$pi;
    }

    (bless \$sum)->add(_set_int(_next_prime($s))->primes_sum(_set_int($n)));
}

sub gpf {
    my ($n) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n >= 0 or goto &nan;
    }

    if ($n eq '0' or $n eq '1') {
        return bless \$n;
    }

    my @f = _factor($n);
    _set_int($f[-1]);
}

sub gpf_sum {    # sum of gpf(k)
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $to->gpf_sum->sub($from->dec->gpf_sum);
    }

    my $n = $$from;

    if (ref($n)) {
        $n = _big2istr($n) // goto &nan;
    }

    if ($n < 0) {
        return ZERO;
    }

    my $s = Math::Prime::Util::GMP::sqrtint($n);

    if ($s > ULONG_MAX) {    # too large
        goto &nan;
    }

    if (HAS_PRIME_UTIL and ((INTSIZE >= 64) ? ($n <= 1e10) : ($n <= 1e5))) {

        my $sum = 0;

        foreach my $p (@{Math::Prime::Util::primes($s)}) {
            $sum += $p * Math::Prime::Util::smooth_count(CORE::int($n / $p), $p);
        }

        for (my $p = Math::Prime::Util::next_prime($s) ; $p <= $n ; $p = Math::Prime::Util::next_prime($p)) {

            my $u = CORE::int($n / $p);
            my $r = CORE::int($n / $u);

            $sum += $u * (
                          ($r - $p < PRIMESUM_MIN)
                          ? Math::Prime::Util::sum_primes($p, $r)
                          : ${_set_int($p)->primes_sum(_set_int($r))}
                         );
            $p = $r;
        }

        return _set_int($sum);
    }

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $p (@{_primes(2, $s)}) {
        my $d = Math::Prime::Util::GMP::divint($n, $p);
        my $r =
          (HAS_PRIME_UTIL and $d < ULONG_MAX)
          ? Math::Prime::Util::smooth_count($d, $p)
          : ${(bless \$p)->smooth_count(_set_int($d))};
        my $w = Math::Prime::Util::GMP::mulint($r, $p);
        if (FAST_MODE and $w < ULONG_MAX) {
            Math::GMPz::Rmpz_add_ui($sum, $sum, $w);
        }
        else {
            state $t = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_set_str($t, "$w", 10);
            Math::GMPz::Rmpz_add($sum, $sum, $t);
        }
    }

    if ($n > ULONG_MAX) {
        $n = Math::GMPz::Rmpz_init_set_str("$n", 10);
    }

    for (my $p = _next_prime($s) ; $p <= $n ; $p = _next_prime($p)) {

        my $u = Math::Prime::Util::GMP::divint($n, $p);
        my $r = Math::Prime::Util::GMP::divint($n, $u);

        my $w = Math::Prime::Util::GMP::mulint($u, _set_int($p)->primes_sum(_set_int($r)));

        if (FAST_MODE and $w < ULONG_MAX) {
            Math::GMPz::Rmpz_add_ui($sum, $sum, $w);
        }
        else {
            state $t = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_set_str($t, "$w", 10);
            Math::GMPz::Rmpz_add($sum, $sum, $t);
        }

        $p = $r;
    }

    bless \$sum;
}

sub gcd_factors {
    my ($n, $arr) = @_;

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_sgn($n) > 0
      or return _array();

    my $z = Math::GMPz::Rmpz_init_set($n);    # copy
    state $t = Math::GMPz::Rmpz_init_nobless();

    my @gcds;
    my %seen_k;
    my %seen_gcd;

    foreach my $k (@$arr) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        my $m = _any2mpz($$k) // next;
        next if $seen_k{Math::GMPz::Rmpz_get_str($m, 16)}++;
        Math::GMPz::Rmpz_gcd($t, $z, $m);
        Math::GMPz::Rmpz_cmp_ui($t, 1) > 0 or next;
        Math::GMPz::Rmpz_cmp($t, $z) < 0   or next;
        if (!$seen_gcd{Math::GMPz::Rmpz_get_str($t, 16)}++) {
            push @gcds, Math::GMPz::Rmpz_init_set($t);
        }
    }

    @gcds = sort { Math::GMPz::Rmpz_cmp($a, $b) } @gcds;

    my @factors;

    foreach my $g (@gcds) {

        Math::GMPz::Rmpz_gcd($t, $g, $z);
        Math::GMPz::Rmpz_cmp_ui($t, 1) > 0 or next;
        Math::GMPz::Rmpz_cmp($t, $z) < 0   or next;

        my $v = Math::GMPz::Rmpz_remove($z, $z, $t);
        push(@factors, (Math::GMPz::Rmpz_init_set($t)) x $v);
    }

    if (Math::GMPz::Rmpz_cmp_ui($z, 1) > 0) {
        push @factors, $z;
    }

    @factors = sort { Math::GMPz::Rmpz_cmp($a, $b) } @factors;
    @factors = map  { bless \$_ } @factors;

    _array(\@factors);
}

*gcd_factor = \&gcd_factors;

sub fibonacci_factor {
    my ($n, $upto) = @_;

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    $upto =
      defined($upto)
      ? do { _any2ui($$upto) // return _array(bless \$n) }
      : CORE::int(1.4747698 * __ilog__($n, 2));

    my @factors;
    my $g = Math::GMPz::Rmpz_init();

    my ($P, $Q) = (3, 1);

    my $U0 = Math::GMPz::Rmpz_init_set_ui(0);
    my $U1 = Math::GMPz::Rmpz_init_set_ui(1);

    my $V0 = Math::GMPz::Rmpz_init_set_ui(2);
    my $V1 = Math::GMPz::Rmpz_init_set_ui($P);

    foreach my $k (2 .. $upto) {

        # my ($U, $V) = Math::Prime::Util::GMP::lucas_sequence($n, $P, $Q, $k);

        Math::GMPz::Rmpz_set($g, $U1);
        Math::GMPz::Rmpz_mul_ui($U1, $U1, $P);
        Math::GMPz::Rmpz_submul_ui($U1, $U0, $Q);
        Math::GMPz::Rmpz_mod($U1, $U1, $n);
        Math::GMPz::Rmpz_set($U0, $g);

        Math::GMPz::Rmpz_set($g, $V1);
        Math::GMPz::Rmpz_mul_ui($V1, $V1, $P);
        Math::GMPz::Rmpz_submul_ui($V1, $V0, $Q);
        Math::GMPz::Rmpz_mod($V1, $V1, $n);
        Math::GMPz::Rmpz_set($V0, $g);

        foreach my $param ([$U1, 0], [$V1, -$P, -2 * $Q, 0]) {

            my ($t, @deltas) = @$param;

            foreach my $delta (@deltas) {

                ($delta >= 0)
                  ? Math::GMPz::Rmpz_add_ui($g, $t, $delta)
                  : Math::GMPz::Rmpz_sub_ui($g, $t, -$delta);

                Math::GMPz::Rmpz_gcd($g, $g, $n);

                if (    Math::GMPz::Rmpz_cmp_ui($g, 1) > 0
                    and Math::GMPz::Rmpz_cmp($g, $n) < 0) {
                    my $r = Math::GMPz::Rmpz_init_set($g);
                    push @factors, bless \$r;
                }
            }
        }
    }

    if (!@factors) {
        return _array(bless \$n);
    }

    (bless \$n)->gcd_factors(_array(\@factors));
}

*fib_factor = \&fibonacci_factor;

sub sophie_germain_factor {
    my ($n) = @_;

    # A simple factorization method, based on Sophie Germain's identity:
    #   x^4 + 4y^4 = (x^2 + 2xy + 2y^2) * (x^2 - 2xy + 2y^2)

    # This method is also effective for numbers of the form: n^4 + 4^(2k+1).

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    state $w = Math::GMPz::Rmpz_init_nobless();
    state $z = Math::GMPz::Rmpz_init_nobless();

    my $sophie_germain_decomposition = sub {
        my ($n) = @_;

        state $t = Math::GMPz::Rmpz_init_nobless();
        state $u = Math::GMPz::Rmpz_init_nobless();

        Math::GMPz::Rmpz_root($t, $n, 4);
        Math::GMPz::Rmpz_pow_ui($w, $t, 4);
        Math::GMPz::Rmpz_sub($u, $n, $w);
        Math::GMPz::Rmpz_div_2exp($u, $u, 2);

        if (Math::GMPz::Rmpz_root($u, $u, 4)) {

            # n = t^4 + 4*u^4
            Math::GMPz::Rmpz_pow_ui($z, $u, 4);
            Math::GMPz::Rmpz_mul_2exp($z, $z, 2);
            Math::GMPz::Rmpz_add($w, $w, $z);

            if (Math::GMPz::Rmpz_cmp($w, $n) == 0) {
                return ($t, $u);
            }
        }

        Math::GMPz::Rmpz_mul_2exp($t, $n, 2);
        Math::GMPz::Rmpz_root($t, $t, 4);
        Math::GMPz::Rmpz_div_2exp($t, $t, 1);
        Math::GMPz::Rmpz_pow_ui($z, $t, 4);
        Math::GMPz::Rmpz_mul_2exp($z, $z, 2);
        Math::GMPz::Rmpz_sub($u, $n, $z);

        if (Math::GMPz::Rmpz_root($u, $u, 4)) {

            # n = u^4 + 4*t^4
            Math::GMPz::Rmpz_pow_ui($w, $u, 4);
            Math::GMPz::Rmpz_add($w, $w, $z);

            if (Math::GMPz::Rmpz_cmp($w, $n) == 0) {
                return ($u, $t);
            }
        }

        return;
    };

    my ($x, $y) = $sophie_germain_decomposition->($n);

    if (!defined($x) or !defined($y)) {
        return _array([bless \$n]);
    }

    my $p = Math::GMPz::Rmpz_init();
    my $q = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_mul($w, $x, $y);
    Math::GMPz::Rmpz_mul_2exp($w, $w, 1);
    Math::GMPz::Rmpz_mul($z, $x, $x);

    Math::GMPz::Rmpz_sub($p, $z, $w);
    Math::GMPz::Rmpz_add($q, $z, $w);

    Math::GMPz::Rmpz_mul($w, $y, $y);
    Math::GMPz::Rmpz_mul_2exp($w, $w, 1);

    Math::GMPz::Rmpz_add($p, $p, $w);
    Math::GMPz::Rmpz_add($q, $q, $w);

    return _array([(bless \$p), (bless \$q)]);
}

*germain_factor = \&sophie_germain_factor;

sub _is_small_number {
    my ($z) = @_;
    return Math::GMPz::Rmpz_fits_ulong_p($z)
      || Math::GMPz::Rmpz_sizeinbase($z, 2) <= SMALL_NUMBER_MAX_BITS;
}

sub _get_recursive_factors {
    my ($factor, $rec_tried, $n) = @_;

    return () if ($rec_tried->{$$factor}++ || $factor->sqr->gt($n));

    say STDERR "Recursively factoring: $$factor" if $VERBOSE;
    return @{$factor->special_factors};
}

sub _is_trivial_factor {
    my ($factor) = @_;
    return (!ref($$factor) or Math::GMPz::Rmpz_sizeinbase($$factor, 2) <= SMALL_NUMBER_MAX_BITS or _is_prob_prime($$factor));
}

sub _create_collector {
    my ($factors, $rec_tried, $factorized_ref, $n) = @_;

    return sub {
        my ($f) = @_;
        my @arr = @$f;
        my $rem = pop(@arr);

        return unless @arr;
        push(@$factors, @arr);
        push(@arr,      $rem);

        while (@arr) {
            my $factor = shift(@arr);

            if (_is_trivial_factor($factor)) {
                $$factorized_ref ||= 1;
            }
            else {
                my @new_factors = _get_recursive_factors($factor, $rec_tried, $n);

                if (@new_factors >= 2) {
                    push @arr,      @new_factors;
                    push @$factors, @new_factors;
                }
                elsif ($factor->is_perfect_power) {
                    my @perfect = (($factor->perfect_root) x CORE::int($factor->perfect_power));
                    push @arr,      @perfect;
                    push @$factors, @perfect;
                }
                else {
                    $$factorized_ref = 0;
                    last;
                }
            }
        }
    };
}

sub _try_factorization_methods {
    my ($n, $mp1, $collector, $factorized_ref) = @_;

    my @methods = (
                   sub { $n->is_perfect_power ? [($n->perfect_root) x CORE::int($n->perfect_power)] : undef },
                   sub { $n->trial_factor($mp1->mul(_set_int(1e5))) },
                   sub { $n->germain_factor },
                   sub { $n->holf_factor($mp1->mul(_set_int(1e4))) },
                   sub { $n->fermat_factor($mp1->mul(_set_int(1e3))) },
                   ($n->len->lt(_set_int(500)) ? sub { $n->flt_factor($mp1->mul(_set_int(1e2))) } : ()),
                   sub { $n->phi_finder_factor($mp1->mul(_set_int(1e3))) },
                   sub { $n->dop_factor($mp1->mul($n->ilog2->isqrt)->mul(TWO)) },
                   sub { $n->miller_factor($mp1->mul(_set_int(5))) },
                   sub { $n->fibonacci_factor },
                   sub { $n->lucas_factor(undef, $mp1->mul(TWO)) },
                   sub { $n->cop_factor($mp1->mul($n->ilog2->isqrt->shift_right(ONE))) },
                   sub { $n->pell_factor($mp1->mul(_set_int(1e3))) },
                  );

    for my $method (@methods) {
        last if $$factorized_ref;
        my $result = $method->();
        $collector->($result) if defined($result);
    }
}

sub _separate_factors {
    my ($factors) = @_;
    my (@prime, @composite);

    for my $f (@$factors) {
        if (_is_prob_prime($$f)) {
            push @prime, $f;
        }
        elsif (!ref($$f) or Math::GMPz::Rmpz_sizeinbase($$f, 2) <= SMALL_NUMBER_MAX_BITS) {
            local $SPECIAL_FACTORS = 0;
            push @prime, map { _set_int($_) } _factor($$f);
        }
        else {
            push @composite, $f;
        }
    }

    return (\@prime, \@composite);
}

sub _build_factorization_blocks {
    my ($m) = @_;

    return (
        Sidef::Types::Block::Block->new(code => sub { $_[0]->fermat_factor($m->mul(_set_int(1e3))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->holf_factor($m->mul(_set_int(1e4))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->pell_factor($m->mul(_set_int(5e2))) }),

        #Sidef::Types::Block::Block->new(code => sub { $_[0]->flt_factor(_set_int(_random_prime(1e4)), $m->mul(_set_int(1e2))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->pm1_factor($m->mul(_set_int(20000)), $m->mul(_set_int(200000))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->ecm_factor($m->mul(_set_int(600)),  $m->mul(_set_int(20))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->ecm_factor($m->mul(_set_int(2000)), $m->mul(_set_int(10))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->pm1_factor($m->mul(_set_int(1e5))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->pp1_factor($m->mul(_set_int(5e4))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->pbrent_factor($m->mul(_set_int(1e5))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->chebyshev_factor($m->mul(_set_int(5e3))) }),
        Sidef::Types::Block::Block->new(code => sub { $_[0]->phi_finder_factor($m->mul(_set_int(1e2))) }),
    );
}

sub _apply_recursive_methods {
    my ($composite, $m) = @_;

    state $methods = {};
    $methods->{$m} //= [_build_factorization_blocks($m)];

    for my $block (@{$methods->{$m}}) {
        $composite = [map { @{$_->factor($block)} } @$composite];
    }

    # Apply cyclotomic factorization for m >= 2 (however, it's too slow)
#<<<
    #~ if ($m->ge(TWO)) {
        #~ $composite = [
            #~ map {
                #~ _is_prob_prime($$_)
                  #~ ? $_
                  #~ : @{$_->cyclotomic_factor(map { _set_int($_) } 2 .. CORE::int($m->mul(_set_int(5))))}
              #~ } @$composite
        #~ ];
    #~ }
#>>>

    return $composite;
}

sub _factorize_small {
    my ($z) = @_;
    local $SPECIAL_FACTORS = 0;

    my @factors =
      Math::GMPz::Rmpz_fits_ulong_p($z)
      ? map { bless \$_ } _factor(Math::GMPz::Rmpz_get_ui($z))
      : map { _set_int($_) } _factor(Math::GMPz::Rmpz_get_str($z, 10));

    return _array(\@factors);
}

sub special_factors {
    my ($n, $m) = @_;

    # Initialize and validate parameters
    $m = defined($m) ? do { ref($m) eq __PACKAGE__ or _valid(\$m); $m } : ONE;
    my $z = _any2mpz($$n) // return _array();

    return _array() if Math::GMPz::Rmpz_sgn($z) <= 0;

    # Handle small numbers directly
    return _factorize_small($z) if _is_small_number($z);

    # Handle prime numbers
    return _array([bless \$z]) if _is_prob_prime($z);

    # Main factorization
    my (@factors, %rec_tried, $factorized);
    my $mp1 = $m->inc;

    # Collect factors using various methods
    my $collect_factors = _create_collector(\@factors, \%rec_tried, \$factorized, $n);

    _try_factorization_methods($n, $mp1, $collect_factors, \$factorized);

    # Process factors
    @factors = @{$n->gcd_factors(_array([@factors]))};
    my ($prime_factors, $composite_factors) = _separate_factors(\@factors);

    # Apply recursive methods if needed
    $composite_factors = _apply_recursive_methods($composite_factors, $m)
      if (@$composite_factors && $m->is_positive);

    return $n->gcd_factors(_array([@$prime_factors, @$composite_factors]));
}

*special_factor = \&special_factors;

sub factor {
    my ($n, $block) = @_;

    if (defined($block)) {

        my %cache;

        my $f = _array([$n])->recmap(
            Sidef::Types::Block::Block->new(
                code => sub {
                    my ($n) = @_;
                    _is_prob_prime($$n) ? _array() : do {
                        my $factors = do { $cache{"$n"} //= $block->run($n) };
                        $factors->first(-1)->concat($factors->last(-1))->iuniq;
                    };
                }
            )
        );
        return $n->gcd_factors($f);
    }

    $n = $$n;

    if (ref($n)) {
        $n = _big2pistr($n) // return _array();
    }
    else {
        $n > 0 or return _array();
    }

    _array([map { _set_int($_) } _factor($n)]);
}

*factors       = \&factor;
*prime_factors = \&factor;

sub factor_exp {
    my ($n) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2pistr($n) // return _array();
    }
    else {
        $n > 0 or return _array();
    }

    my @pairs;
    foreach my $pk (_factor_exp($n)) {
        my ($p, $k) = @$pk;
        push @pairs, bless([(($p < ULONG_MAX) ? (bless \$p) : _set_int($p)), (bless \$k)], 'Sidef::Types::Array::Array');
    }

    _array(\@pairs);
}

*factors_exp = \&factor_exp;

sub prime_signature {
    my ($n) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2pistr($n) // return _array();
    }
    else {
        $n > 0 or return _array();
    }

    if (HAS_NEW_PRIME_UTIL and $n < ULONG_MAX) {
        my @exp = Math::Prime::Util::prime_signature($n);
        @exp = map { ($_ == 1) ? ONE : bless \$_ } @exp;
        return _array(\@exp);
    }

    my @exp = sort { $b <=> $a } map { $_->[1] } _factor_exp($n);
    @exp = map { ($_ == 1) ? ONE : bless \$_ } @exp;
    _array(\@exp);
}

sub _factor_remainder {
    my ($r, $size, $ecm_table) = @_;

    if (ref($r) eq 'Math::GMPz') {
        $r = Math::GMPz::Rmpz_get_str($r, 10);
    }

    # Check primality
    if (_is_prob_prime($r)) {
        return ($r);
    }

    # Check perfect power
    if (my $k = _power_factor($r)) {
        my $base = Math::Prime::Util::GMP::rootint($r, $k);
        say STDERR "is_power(r): $base^$k" if $VERBOSE;
        return ((_factor_remainder($base, $size, $ecm_table)) x $k);
    }

    # Recalculate size for remainder
    my $max_size = (Math::Prime::Util::GMP::logint($r, 2) >> 1) + 1;
    if ($size > $max_size) {
        $size = $max_size;
    }

    my $limit_isqrt = Math::Prime::Util::GMP::powint(2, (($size >> 1) + 1));
    $limit_isqrt = 100 if ($limit_isqrt < 100);

    my @ecm_params;

    # ECM parameter selection
    foreach my $i (0 .. CORE::int(@$ecm_table / 3 - 1)) {

        my ($B1, $curves) = (@{$ecm_table}[3 * $i + 1, 3 * $i + 2]);

        # Scale curves with difficulty
        my $scale_factor = ($size > 100) ? 2.5 : 2.0;
        $curves = CORE::int($curves * $scale_factor);

        push @ecm_params, [$B1, $curves];

        if ($ecm_table->[3 * $i] >= $size) {
            last;
        }
    }

    my @factors;

    while (1) {
        my @f;

        # Early termination for primes
        if (_is_prob_prime($r)) {
            last;
        }

        if ($r < 0xffffffff) {    # 2^32 - 1
            @f =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::factor($r)
              : Math::Prime::Util::GMP::factor($r);
            $r = 1;
            say STDERR "factor(r): @f" if $VERBOSE;
        }

        # Method selection based on expected factor size
        elsif ($size < 20) {

            # Very small: use Pollard Rho
            my $effort = Math::Prime::Util::GMP::mulint(2, $limit_isqrt);
            @f = Math::Prime::Util::GMP::prho_factor($r, $effort);
            $r = pop @f;
            say STDERR "prho_factor(r, $effort): @f" if (@f && $VERBOSE);
        }
        elsif ($size < 35) {

            # Small to medium: try Brent first, then Rho
            my $effort = Math::Prime::Util::GMP::mulint(2, $limit_isqrt);
            @f = Math::Prime::Util::GMP::pbrent_factor($r, $effort);
            $r = pop @f;
            say STDERR "pbrent_factor(r, $effort): @f" if (@f && $VERBOSE);

            if (!@f) {
                @f = Math::Prime::Util::GMP::prho_factor($r, $effort);
                $r = pop @f;
                say STDERR "prho_factor(r, $effort): @f" if (@f && $VERBOSE);
            }
        }
        else {
            while (defined(my $ecm_param = shift(@ecm_params))) {
                my ($B1, $curves) = @$ecm_param;
                say STDERR "Trying ecm_factor(r, $B1, $curves)" if $VERBOSE;
                my @tmp_f = Math::Prime::Util::GMP::ecm_factor($r, $B1, $curves);

                if (scalar(@tmp_f) >= 2) {
                    unshift(@ecm_params, $ecm_param);
                    @f = @tmp_f;
                    $r = pop @f;
                    say STDERR "ecm_factor(r, $B1, $curves): @f" if (@f && $VERBOSE);
                    last;
                }
            }
        }

        if (!@f) {
            last;
        }

        my @new_factors;

        foreach my $factor (@f) {
            if (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime($factor) : Math::Prime::Util::GMP::is_prime($factor)) {
                push @new_factors, $factor;
            }
            else {
                push @new_factors, _factor($factor);
            }
        }

        push @factors, @new_factors;

        # Batch process all factors efficiently
        if (@new_factors) {

            # Build product of all new factors
            my $product = Math::Prime::Util::GMP::vecprod(@new_factors);

            # Use GCD to find common factors efficiently
            my $g = Math::Prime::Util::GMP::gcd($r, $product);

            if ($g ne '1' and $g ne $r) {

                # Find valuation for each prime in the GCD
                foreach my $p (@new_factors) {
                    my $v = Math::Prime::Util::GMP::valuation($r, $p);
                    if ($v > 0) {
                        push @factors, ($p) x $v;
                        $r = Math::Prime::Util::GMP::divint($r, Math::Prime::Util::GMP::powint($p, $v));
                    }
                }
            }
        }

        # Recalculate size for remainder
        my $max_size = (Math::Prime::Util::GMP::logint($r, 2) >> 1) + 1;
        if ($size > $max_size) {
            $size = $max_size;
        }
    }

    if ($r ne '1') {
        push @factors, $r;
    }

    return @factors;
}

sub factor_upto {
    my ($n, $limit) = @_;

    # Reference:
    #   https://members.loria.fr/PZimmermann/records/ecm/params.html
    state $ecm_table = [
        qw(
          30      1358        2
          35      1270        5
          40      1629        10
          45      4537        10
          50      12322       9
          55      12820       18
          60      21905       21
          65      24433       41
          70      32918       66
          75      64703       71
          80      76620       119
          85      155247      123
          90      183849      219
          95      245335      321
          100     445657      339
          105     643986      468
          110     1305195     439
          115     1305195     818
          120     3071166     649
          125     3784867     949
          130     4572523     1507
          135     7982718     1497
          140     9267681     2399
          145     22025673    1826
          150     22025673    3159
          155     26345943    4532
          160     35158748    6076
          165     46919468    8177
          170     47862548    14038
          175     153319098   7166
          180     153319098   12017
          185     188949210   16238
          190     410593604   13174
          195     496041799   17798
          200     491130495   29584
          205     1067244762  23626
          210     1056677983  38609
          215     1328416470  49784
          220     1315263832  81950
          225     2858117139  63461
          )
    ];

    my $r = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_sgn($r) > 0
      or return _array();

    if (defined($limit)) {
        $limit = _big2pistr($$limit) // return _array([$n]);
    }
    else {
        $limit = Math::Prime::Util::GMP::sqrtint($r);
    }

    my $size     = Math::Prime::Util::GMP::logint($limit, 2) + 1;
    my $max_size = (Math::GMPz::Rmpz_sizeinbase($r, 2) >> 1) + 1;    # ilog_2(sqrt(r))

    if ($size > $max_size) {
        $size = $max_size;
    }

    my @factors;

    # Trial division first for small factors
    if ($size > 13) {
        my $trial_limit = ($size > 16) ? (1 << 16) : (1 << $size);

        ($r, @factors) = _primorial_trial_factor($r, $trial_limit);

        if (@factors > 0) {

            # If fully factored, return result
            if (Math::GMPz::Rmpz_cmp_ui($r, 1) == 0) {
                return _array([map { bless \$_ } @factors]);
            }
        }
    }
    else {
        return $n->trial_factor(_set_int(1 << $size));
    }

    # Factor the remainder
    push @factors, _factor_remainder($r, $size, $ecm_table);
    _array([map { _set_int($_) } @factors])->isort;
}

sub trial_factor {
    my ($n, $k) = @_;

    if (!defined($k)) {
        $n = _big2pistr($$n) // (return _array());
        my ($r, @factors) = _adaptive_trial_factor($n);
        @factors = map { bless \$_ } @factors;
        if (Math::GMPz::Rmpz_cmp_ui($r, 1) > 0) {
            $r = Math::GMPz::Rmpz_get_ui($r) if Math::GMPz::Rmpz_fits_ulong_p($r);
            push @factors, bless \$r;
        }
        return _array(\@factors);
    }

    ref($k) eq __PACKAGE__ or _valid(\$k);
    __is_int__($$n) || return _array();

    $n = _any2mpz($$n) // return _array();
    $k = _any2ui($$k)  // return _array();

    return _array()               if Math::GMPz::Rmpz_sgn($n) <= 0;
    return _array([_set_int($n)]) if $k <= 0;

    my ($r, @factors) = _primorial_trial_factor($n, $k);

    @factors = map { bless \$_ } @factors;

    if (Math::GMPz::Rmpz_cmp_ui($r, 1) > 0) {
        $r = Math::GMPz::Rmpz_get_ui($r) if Math::GMPz::Rmpz_fits_ulong_p($r);
        push @factors, bless \$r;
    }

    return _array(\@factors);
}

sub prho_factor {
    my ($n, $k) = @_;
    (ref($k) eq __PACKAGE__ or _valid(\$k)) if defined($k);
    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::prho_factor(_big2pistr($$n) // (return _array()), (defined($k) ? (_big2pistr($$k) || return _array($n)) : ()),)
           ]
          );
}

*rho_factor = \&prho_factor;

sub pbrent_factor {
    my ($n, $k) = @_;
    (ref($k) eq __PACKAGE__ or _valid(\$k)) if defined($k);
    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::pbrent_factor(_big2pistr($$n) // (return _array()), (defined($k) ? (_big2pistr($$k) || return _array($n)) : ()))
           ]
          );
}

*rho_brent_factor = \&pbrent_factor;

sub pminus1_factor {
    my ($n, $B1, $B2) = @_;

    (ref($B1) eq __PACKAGE__ or _valid(\$B1)) if defined($B1);
    (ref($B2) eq __PACKAGE__ or _valid(\$B2)) if defined($B2);

    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::pminus1_factor(
                                                     _big2pistr($$n) // (return _array()),
                                                     (defined($B1) ? (_big2pistr($$B1) || return _array($n)) : ()),
                                                     (defined($B2) ? (_big2pistr($$B2) || return _array($n)) : ())
                                                    )
           ]
          );
}

*pm1_factor = \&pminus1_factor;

sub pplus1_factor {
    my ($n, $B1) = @_;
    (ref($B1) eq __PACKAGE__ or _valid(\$B1)) if defined($B1);
    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::pplus1_factor(_big2pistr($$n) // (return _array()), (defined($B1) ? (_big2pistr($$B1) || return _array($n)) : ()))
           ]
          );
}

*pp1_factor = \&pplus1_factor;

sub chebyshev_factor {
    my ($n, $B, $x) = @_;

    # The Chebyshev factorization method, taking
    # advantage of the smoothness of p-1 or p+1.

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    $B =
      defined($B)
      ? do { ref($B) eq __PACKAGE__ or _valid(\$B); _any2ui($$B) || return _array(bless \$n) }
      : 1e5;

    $x =
      defined($x)
      ? do { ref($x) eq __PACKAGE__ or _valid(\$x); Math::GMPz::Rmpz_init_set(_any2mpz($$x) // $TWO) }
      : Math::GMPz::Rmpz_init_set_ui(CORE::int(CORE::rand(1e9)));

    my $i = Math::GMPz::Rmpz_init_set_ui(2);

    # Try to compute invmod(2, n)
    # If n is even, return faster
    Math::GMPz::Rmpz_invert($i, $i, $n)
      || return _array([TWO, (($n == 2) ? () : _set_int($n >> 1))]);

    state $V1 = Math::GMPz::Rmpz_init_nobless();
    state $V2 = Math::GMPz::Rmpz_init_nobless();

    my $chebyshevTmod = sub {
        my ($v, $x) = @_;

        Math::GMPz::Rmpz_mul_2exp($x, $x, 1);

        Math::GMPz::Rmpz_set_ui($V1, 2);
        Math::GMPz::Rmpz_set($V2, $x);

        my @bits;
        while ($v) {
            unshift @bits, $v & 1;
            $v >>= 1;
        }

        foreach my $bit (@bits) {

            if ($bit) {
                Math::GMPz::Rmpz_mul($V1, $V1, $V2);
                Math::GMPz::Rmpz_powm_ui($V2, $V2, 2, $n);
                Math::GMPz::Rmpz_sub($V1, $V1, $x);
                Math::GMPz::Rmpz_sub_ui($V2, $V2, 2);
                Math::GMPz::Rmpz_mod($V1, $V1, $n);
            }
            else {
                Math::GMPz::Rmpz_mul($V2, $V2, $V1);
                Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $n);
                Math::GMPz::Rmpz_sub($V2, $V2, $x);
                Math::GMPz::Rmpz_sub_ui($V1, $V1, 2);
                Math::GMPz::Rmpz_mod($V2, $V2, $n);
            }
        }

        Math::GMPz::Rmpz_mul($x, $V1, $i);
        Math::GMPz::Rmpz_mod($x, $x, $n);
    };

    my $g     = Math::GMPz::Rmpz_init();
    my $lnB   = CORE::log($B);
    my $sqrtB = Math::Prime::Util::GMP::sqrtint($B);

    foreach my $p (@{_primes(2, $sqrtB)}) {
        $chebyshevTmod->($p**CORE::int($lnB / CORE::log($p)), $x);
    }

    for (my $p = _next_prime($sqrtB) ; $p <= $B ; $p = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p))) {

        $chebyshevTmod->($p, $x);    # T_k(x) (mod n)

        Math::GMPz::Rmpz_sub_ui($g, $x, 1);
        Math::GMPz::Rmpz_gcd($g, $g, $n);

        if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

            if (Math::GMPz::Rmpz_cmp($g, $n) == 0) {
                return _array([bless \$n]);
            }

            my $x = Math::GMPz::Rmpz_init();
            my $y = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_set($y, $g);
            Math::GMPz::Rmpz_divexact($x, $n, $g);

            my @f = map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } ($x, $y);
            return _array(\@f);
        }
    }

    _array([bless(\$n)]);
}

sub holf_factor {
    my ($n, $k) = @_;
    (ref($k) eq __PACKAGE__ or _valid(\$k)) if defined($k);
    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::holf_factor(_big2pistr($$n) // (return _array()), (defined($k) ? (_big2pistr($$k) || return _array($n)) : 1e4))
           ]
          );
}

sub _miller_factor {
    my ($n, $tries) = @_;

    # $n is a Math::GMPz object holding a positive value
    # $tries is a positive native integer or `undef`

    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _factor(Math::GMPz::Rmpz_get_ui($n));
    }

    if (_is_prob_prime($n)) {
        return ($n);
    }

    state $nm1 = Math::GMPz::Rmpz_init_nobless();    # n-1
    state $d   = Math::GMPz::Rmpz_init_nobless();    # (n-1) >> s

    Math::GMPz::Rmpz_sub_ui($nm1, $n, 1);

    my $s = Math::GMPz::Rmpz_scan1($nm1, 0) || return ($n);
    my $r = $s - 1;

    Math::GMPz::Rmpz_div_2exp($d, $nm1, $s);

    $tries //= 1 + CORE::int(200 / $s);

    state $x = Math::GMPz::Rmpz_init();
    state $g = Math::GMPz::Rmpz_init();
    state $t = Math::GMPz::Rmpz_init();
    state $N = Math::GMPz::Rmpz_init();

    my @all_factors;

    Math::GMPz::Rmpz_set($N, $n);

    for (1 .. $tries) {

        my $p = _random_prime(ULONG_MAX >> 1);
        Math::GMPz::Rmpz_set_ui($g, $p);
        Math::GMPz::Rmpz_powm($x, $g, $d, $n);

        next if (Math::GMPz::Rmpz_cmp_ui($x, 1) == 0);

        foreach my $k (0 .. $r) {

            last if (Math::GMPz::Rmpz_cmp($x, $nm1) == 0);

            # gcd(x +/- 1, n) may be a non-trivial factor of n
            Math::GMPz::Rmpz_sub_ui($g, $x, 1);
            Math::GMPz::Rmpz_gcd($g, $g, $n);

            if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0 and Math::GMPz::Rmpz_cmp($g, $n) < 0) {

                Math::GMPz::Rmpz_gcd($t, $N, $g);

                if (Math::GMPz::Rmpz_cmp_ui($t, 1) > 0 and Math::GMPz::Rmpz_cmp($t, $N) < 0) {
                    if (_is_prob_prime($t)) {
                        Math::GMPz::Rmpz_remove($N, $N, $t);
                        push @all_factors, Math::GMPz::Rmpz_init_set($t);
                    }
                    else {
                        Math::GMPz::Rmpz_divexact($t, $N, $t);
                        if (_is_prob_prime($t)) {
                            Math::GMPz::Rmpz_remove($N, $N, $t);
                            push @all_factors, Math::GMPz::Rmpz_init_set($t);
                        }
                    }
                }

                push @all_factors, Math::GMPz::Rmpz_init_set($g);

                if (scalar(@all_factors) >= 10 or Math::GMPz::Rmpz_fits_ulong_p($N) or _is_prob_prime(Math::GMPz::Rmpz_get_str($N, 10))) {
                    return map { $$_ } @{((bless \$n)->gcd_factors(_array([map { bless \$_ } @all_factors])))};
                }

                # Recursive approach
                #~ Math::GMPz::Rmpz_divexact($t, $n, $g);

                #~ my @g_factors = (_is_prob_prime($g) ? Math::GMPz::Rmpz_init_set($g) : __SUB__->(Math::GMPz::Rmpz_init_set($g)));
                #~ my @t_factors = (_is_prob_prime($t) ? Math::GMPz::Rmpz_init_set($t) : __SUB__->(Math::GMPz::Rmpz_init_set($t)));

                #~ return (@g_factors, @t_factors);
            }

            Math::GMPz::Rmpz_powm_ui($x, $x, 2, $n);
            last if (Math::GMPz::Rmpz_cmp_ui($x, 1) == 0);
        }
    }

    if (@all_factors) {
        return map { $$_ } @{((bless \$n)->gcd_factors(_array([map { bless \$_ } @all_factors])))};
    }

    my @holf_factors = Math::Prime::Util::GMP::holf_factor(Math::GMPz::Rmpz_get_str($n, 10), 10_000);

    if (scalar(@holf_factors) > 1) {
        return (
                map { _is_prob_prime($_) ? $_ : __SUB__->($_) }
                map { Math::GMPz::Rmpz_init_set_str($_, 10) } @holf_factors
               );
    }

    return ($n);
}

sub miller_factor {
    my ($n, $tries) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    if (defined($tries)) {
        ref($tries) eq __PACKAGE__ or _valid(\$tries);
        $tries = _any2ui($$tries) || return _array([_set_int($n)]);
    }

    my @factors = sort { $$a <=> $$b } map { bless \$_ } _miller_factor($n, $tries);

    _array(\@factors);
}

*miller_rabin_factor = \&miller_factor;

sub _lucas_factor {
    my ($n, $j, $tries) = @_;

    # $n is a Math::GMPz object holding a positive value
    # $j is a signed native integer or `undef`
    # $tries is an unsigned native integer or `undef`

    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _factor(Math::GMPz::Rmpz_get_ui($n));
    }

    if (_is_prob_prime($n)) {
        return ($n);
    }

    if (!defined($j)) {
        my @factors = __SUB__->($n, 1, $tries);
        @factors = map { _is_prob_prime($_) ? $_ : __SUB__->($_, -1, $tries) } @factors;
        return @factors;
    }

    state $npj = Math::GMPz::Rmpz_init_nobless();    # n + j

    ($j < 0)
      ? Math::GMPz::Rmpz_sub_ui($npj, $n, -$j)
      : Math::GMPz::Rmpz_add_ui($npj, $n, +$j);

    my $s = Math::GMPz::Rmpz_scan1($npj, 0) || return ($n);

    Math::GMPz::Rmpz_div_2exp($npj, $npj, $s);       # (n+j) >> s
    my $dstr = Math::GMPz::Rmpz_get_str($npj, 10);

    $tries //= 1 + CORE::int(100 / $s);

    my $PQ_limit = $LUCAS_PQ_LIMIT;

    state $g = Math::GMPz::Rmpz_init();
    state $t = Math::GMPz::Rmpz_init();
    state $N = Math::GMPz::Rmpz_init();

    my @all_factors;

    Math::GMPz::Rmpz_set($N, $n);

    my $nstr = Math::GMPz::Rmpz_get_str($n, 10);

    foreach my $i (1 .. $tries) {

        my $P = 1 + CORE::int(CORE::rand($PQ_limit));
        my $Q = 1 + CORE::int(CORE::rand($PQ_limit));

        $Q *= -1 if (CORE::rand(1) < 0.5);

        my $delta = $P * $P - 4 * $Q;

        next
          if (
              HAS_PRIME_UTIL
              ? Math::Prime::Util::is_square($delta)
              : Math::Prime::Util::GMP::is_square($delta)
             );

        my ($U1, $V1, $Q1) =
          map { Math::GMPz::Rmpz_init_set_str($_, 10) } Math::Prime::Util::GMP::lucas_sequence($nstr, $P, $Q, $dstr);

        for my $k (1 .. $s) {

            foreach my $x ($U1, $V1, $P) {

                if (ref($x)) {
                    Math::GMPz::Rmpz_gcd($g, $x, $n);
                }
                else {
                    Math::GMPz::Rmpz_sub_ui($g, $V1, $x);
                    Math::GMPz::Rmpz_gcd($g, $g, $n);
                }

                if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0 and Math::GMPz::Rmpz_cmp($g, $n) < 0) {

                    Math::GMPz::Rmpz_gcd($t, $N, $g);

                    if (Math::GMPz::Rmpz_cmp_ui($t, 1) > 0 and Math::GMPz::Rmpz_cmp($t, $N) < 0) {
                        if (_is_prob_prime($t)) {
                            Math::GMPz::Rmpz_remove($N, $N, $t);
                            push @all_factors, Math::GMPz::Rmpz_init_set($t);
                        }
                        else {
                            Math::GMPz::Rmpz_divexact($t, $N, $t);
                            if (_is_prob_prime($t)) {
                                Math::GMPz::Rmpz_remove($N, $N, $t);
                                push @all_factors, Math::GMPz::Rmpz_init_set($t);
                            }
                        }
                    }

                    push @all_factors, Math::GMPz::Rmpz_init_set($g);

                    if (scalar(@all_factors) >= 5 or Math::GMPz::Rmpz_fits_ulong_p($N) or _is_prob_prime(Math::GMPz::Rmpz_get_str($N, 10))) {
                        return map { $$_ } @{((bless \$n)->gcd_factors(_array([map { bless \$_ } @all_factors])))};
                    }

                    # Recursive approach
                    #~ Math::GMPz::Rmpz_divexact($t, $n, $g);

                    #~ my @g_factors = (_is_prob_prime($g) ? Math::GMPz::Rmpz_init_set($g) : __SUB__->(Math::GMPz::Rmpz_init_set($g)));
                    #~ my @t_factors = (_is_prob_prime($t) ? Math::GMPz::Rmpz_init_set($t) : __SUB__->(Math::GMPz::Rmpz_init_set($t)));

                    #~ return (@g_factors, @t_factors);
                }
            }

            Math::GMPz::Rmpz_mul($U1, $U1, $V1);
            Math::GMPz::Rmpz_mod($U1, $U1, $n);
            Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $n);
            Math::GMPz::Rmpz_submul_ui($V1, $Q1, 2);
            Math::GMPz::Rmpz_powm_ui($Q1, $Q1, 2, $n);
        }
    }

    if (@all_factors) {
        return map { $$_ } @{((bless \$n)->gcd_factors(_array([map { bless \$_ } @all_factors])))};
    }

    my @holf_factors = Math::Prime::Util::GMP::holf_factor($nstr, 10_000);

    if (scalar(@holf_factors) > 1) {
        return (map { _is_prob_prime($_) ? $_ : __SUB__->($_, $j) } map { Math::GMPz::Rmpz_init_set_str($_, 10) } @holf_factors);
    }

    return ($n);
}

sub lucas_factor {
    my ($n, $j, $tries) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    if (defined($j)) {
        ref($j) eq __PACKAGE__ or _valid(\$j);
        $j = _any2si($$j) // undef;
    }

    if (defined($tries)) {
        ref($tries) eq __PACKAGE__ or _valid(\$tries);
        $tries = _any2ui($$tries) || return _array([_set_int($n)]);
    }

    my @factors = sort { $$a <=> $$b } map { bless \$_ } _lucas_factor($n, $j, $tries);

    _array(\@factors);
}

*lucas_miller_factor = \&lucas_factor;

sub fermat_factor {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return _array();
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) || return _array([_set_int($n)]) } : 1e4;

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    my $p = Math::GMPz::Rmpz_init();    # p = floor(sqrt(n))
    my $q = Math::GMPz::Rmpz_init();    # q = p^2 - n

    Math::GMPz::Rmpz_sqrtrem($p, $q, $n);
    Math::GMPz::Rmpz_neg($q, $q);

    foreach my $j (1 .. $k) {

        Math::GMPz::Rmpz_addmul_ui($q, $p, 2);

        Math::GMPz::Rmpz_add_ui($q, $q, 1);
        Math::GMPz::Rmpz_add_ui($p, $p, 1);

        if (Math::GMPz::Rmpz_perfect_square_p($q)) {
            Math::GMPz::Rmpz_sqrt($q, $q);

            my $r1 = Math::GMPz::Rmpz_init();
            my $r2 = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_sub($r1, $p, $q);
            Math::GMPz::Rmpz_add($r2, $p, $q);

            return _array([bless(\$r1), bless(\$r2)]);
        }
    }

    _array([_set_int($n)]);
}

sub phi_finder_factor {
    my ($n, $k) = @_;

    # Phi-finder algorithm for semiprimes, due to Kyle Kloster (2010)

    $n = _any2mpz($$n) // return _array();
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) || return _array(bless \$n) } : 1e4;

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    my $E = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sqrt($E, $n);
    Math::GMPz::Rmpz_mul_2exp($E, $E, 1);
    Math::GMPz::Rmpz_sub($E, $n, $E);
    Math::GMPz::Rmpz_add_ui($E, $E, 1);

    my $E0 = Math::GMPz::Rmpz_init_set_ui(2);
    while (Math::GMPz::Rmpz_divisible_p($n, $E0)) {
        Math::GMPz::Rmpz_nextprime($E0, $E0);
    }
    Math::GMPz::Rmpz_powm($E0, $E0, -$E, $n);

    my $i = 0;
    my $L = Math::GMPz::Rmpz_sizeinbase($n, 2) - 1;

    # Repeat until E0 is a power of 2
    while (Math::GMPz::Rmpz_popcount($E0) != 1) {
        Math::GMPz::Rmpz_mul_2exp($E0, $E0, $L);
        Math::GMPz::Rmpz_mod($E0, $E0, $n);
        return _array(bless \$n) if (++$i > $k);
    }

    my $t = 0;
    my $u = Math::GMPz::Rmpz_init();

    foreach my $j (0 .. $L) {
        Math::GMPz::Rmpz_set_ui($u, 2);
        Math::GMPz::Rmpz_powm_ui($u, $u, $j, $n);
        if (Math::GMPz::Rmpz_cmp($u, $E0) == 0) {
            $t = $j;
            last;
        }
    }

    my $phi = Math::GMPz::Rmpz_init_set_ui($i * $L);
    Math::GMPz::Rmpz_sub($phi, $phi, $E);
    Math::GMPz::Rmpz_sub_ui($phi, $phi, $t);
    Math::GMPz::Rmpz_abs($phi, $phi);

    my $q = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub($q, $n, $phi);
    Math::GMPz::Rmpz_add_ui($q, $q, 1);
    Math::GMPz::Rmpz_mul($E, $q, $q);
    Math::GMPz::Rmpz_submul_ui($E, $n, 4);
    Math::GMPz::Rmpz_abs($E, $E);
    Math::GMPz::Rmpz_sqrt($E, $E);
    Math::GMPz::Rmpz_add($E, $E, $q);
    Math::GMPz::Rmpz_div_2exp($E, $E, 1);
    Math::GMPz::Rmpz_gcd($E, $E, $n);

    if (Math::GMPz::Rmpz_cmp_ui($E, 1) > 0 and Math::GMPz::Rmpz_cmp($n, $E) > 0) {
        Math::GMPz::Rmpz_divexact($q, $n, $E);
        my @factors = map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } ($E, $q);
        return _array(\@factors);
    }

    _array(bless \$n);
}

sub _extract_divisors {
    my ($n, $params, $seen_divisor) = @_;

    my ($r1, $e1, $r2, $e2) = @$params;

    my @d1 = _divisors($e1);
    my @d2 = _divisors($e2);

    @d1 = map {
        my $x = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($x, $r1, $_);
        $x;
    } @d1;

    @d2 = map {
        my $y = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_pow_ui($y, $r2, $_);
        $y;
    } @d2;

    state $g = Math::GMPz::Rmpz_init_nobless();

    my @factors;

    foreach my $x (@d1) {
        foreach my $y (@d2) {
            foreach my $j (-1, 1) {

                ($j == 1)
                  ? Math::GMPz::Rmpz_sub($g, $x, $y)
                  : Math::GMPz::Rmpz_add($g, $x, $y);

                Math::GMPz::Rmpz_gcd($g, $g, $n);

                if (    Math::GMPz::Rmpz_cmp_ui($g, 1) > 0
                    and Math::GMPz::Rmpz_cmp($g, $n) < 0
                    and !$seen_divisor->{Math::GMPz::Rmpz_get_str($g, 16)}++) {
                    push @factors, Math::GMPz::Rmpz_init_set($g);
                }
            }
        }
    }

    @factors;
}

# Congruence of powers factorization method
sub cop_factor {
    my ($n, $upto) = @_;

    $n = _any2mpz($$n) // return _array();

    if (defined($upto)) {
        ref($upto) eq __PACKAGE__ or _valid(\$upto);
        $upto = _any2ui($$upto) || return _array([_set_int($n)]);
    }

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    my @congr_powers_params;

    my $process = sub {
        my ($root, $e) = @_;

        for my $j (-1, 0, 1) {

            my $k = Math::GMPz::Rmpz_init();
            my $u = Math::GMPz::Rmpz_init();

            ($j < 0)
              ? Math::GMPz::Rmpz_sub_ui($k, $root, CORE::abs($j))
              : Math::GMPz::Rmpz_add_ui($k, $root, $j);

            Math::GMPz::Rmpz_powm_ui($u, $k, $e, $n);

            foreach my $z ($u, $n - $u) {

                if (Math::GMPz::Rmpz_perfect_power_p($z)) {

                    my $t = _power_factor($z) || 1;

                    if ($t > 1) {
                        my $r = Math::GMPz::Rmpz_init();
                        Math::GMPz::Rmpz_root($r, $z, $t);
                        push @congr_powers_params, [$r, $t, $k, $e];
                    }
                }
            }
        }
    };

    my $n_log2 = Math::GMPz::Rmpz_sizeinbase($n, 2);

    if (defined($upto) and $n_log2 > $upto) {
        $n_log2 = $upto;
    }

    my @range = CORE::reverse(2 .. $n_log2);

    for my $e (@range) {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_root($r, $n, $e);
        $process->($r, $e);
    }

    for my $r (@range) {
        my $e = __ilog__($n, $r);
        $process->(Math::GMPz::Rmpz_init_set_ui($r), $e);
    }

    my %seen_param;
    @congr_powers_params = grep { !$seen_param{join(' ', @$_)}++ } @congr_powers_params;

    my @divisors;
    my %seen_divisor;

    foreach my $args (@congr_powers_params) {
        push @divisors, _extract_divisors($n, $args, \%seen_divisor);
    }

    (bless \$n)->gcd_factors(_array([map { bless \$_ } @divisors]));
}

# Difference of powers factorization method
sub dop_factor {
    my ($n, $upto) = @_;

    $n = _any2mpz($$n) // return _array();

    if (defined($upto)) {
        $upto = _any2ui($$upto) || return _array([_set_int($n)]);
    }

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    my @diff_powers_params;

    my $diff_power_check = sub {
        my ($r1, $e1) = @_;

        # u = r1^e1
        state $u = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_pow_ui($u, $r1, $e1);

        # dx = abs(u - n)
        state $dx = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_sub($dx, $u, $n);
        Math::GMPz::Rmpz_abs($dx, $dx);

        if (Math::GMPz::Rmpz_perfect_power_p($dx)) {

            my $e2 = _power_factor(Math::GMPz::Rmpz_init_set($dx)) || 1;
            my $r2 = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_root($r2, $dx, $e2);
            push @diff_powers_params, [$r1, $e1, $r2, $e2];
        }
    };

    my $n_log2 = Math::GMPz::Rmpz_sizeinbase($n, 2);

    if (defined($upto) and $n_log2 > $upto) {
        $n_log2 = $upto;
    }

    my @range = CORE::reverse(2 .. $n_log2);

    # Sum and difference of powers of the form a^k ± b^k, where a and b are small.
    if (0) {
        foreach my $k (@range) {

            my $t  = __ilog__($n, $k);
            my $r1 = Math::GMPz::Rmpz_init_set_ui($k);

            $diff_power_check->($r1, $t);        # sum of powers
            $diff_power_check->($r1, $t + 1);    # difference of powers
        }
    }

    # Sum and difference of powers of the form a^k ± b^k, where a and b are large.
    foreach my $e1 (@range) {

        my $t = Math::GMPz::Rmpz_init();
        my $u = Math::GMPz::Rmpz_init();

        Math::GMPz::Rmpz_root($t, $n, $e1);
        Math::GMPz::Rmpz_add_ui($u, $t, 1);

        $diff_power_check->($t, $e1);    # sum of powers
        $diff_power_check->($u, $e1);    # difference of powers
    }

    my @divisors;
    my %seen_divisor;

    foreach my $args (@diff_powers_params) {
        push @divisors, _extract_divisors($n, $args, \%seen_divisor);
    }

    (bless \$n)->gcd_factors(_array([map { bless \$_ } @divisors]));
}

# "Fermat's Little Theorem" factorization method
sub flt_factor {
    my ($n, $base, $reps) = @_;

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    $base = defined($base) ? do { ref($base) eq __PACKAGE__ or _valid(\$base); _any2ui($$base) // 2 }                        : 2;
    $reps = defined($reps) ? do { ref($reps) eq __PACKAGE__ or _valid(\$reps); _any2ui($$reps) || return _array(bless \$n) } : 1e4;

    state $z = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();
    state $g = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($z, $base);
    Math::GMPz::Rmpz_set_ui($t, $base);

    Math::GMPz::Rmpz_powm($z, $z, $n, $n);

    # Cannot factor Fermat pseudoprimes
    if (Math::GMPz::Rmpz_cmp_ui($z, $base) == 0) {
        return _array([bless \$n]);
    }

    my $multiplier = $base * $base;

    if ($multiplier > ULONG_MAX) {
        return _array([bless \$n]);
    }

    foreach my $j (1 .. $reps) {

        Math::GMPz::Rmpz_mul_ui($t, $t, $multiplier);
        Math::GMPz::Rmpz_mod($t, $t, $n) if ($j % 10 == 0);
        Math::GMPz::Rmpz_sub($g, $z, $t);
        Math::GMPz::Rmpz_gcd($g, $g, $n);

        if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

            if (Math::GMPz::Rmpz_cmp($g, $n) == 0) {
                return _array([bless \$n]);
            }

            my $x = Math::GMPz::Rmpz_init();
            my $y = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_set($y, $g);
            Math::GMPz::Rmpz_divexact($x, $n, $g);

            my @f = map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } ($x, $y);
            return _array(\@f);
        }
    }

    _array([bless(\$n)]);
}

sub pell_factor {
    my ($n, $reps) = @_;

    # Simple version of the continued-fraction factorization method.
    # Efficient for numbers that have factors relatively close to sqrt(n)

    $n    = _any2mpz($$n) // return _array();
    $reps = defined($reps) ? do { ref($reps) eq __PACKAGE__ or _valid(\$reps); _any2ui($$reps) || return _array([_set_int($n)]) } : 1e4;

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    if (Math::GMPz::Rmpz_perfect_square_p($n)) {
        my $t = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_sqrt($t, $n);
        return _array([(bless \$t), (bless \$t)]);
    }

    my $x = Math::GMPz::Rmpz_init();
    my $y = Math::GMPz::Rmpz_init();
    my $z = Math::GMPz::Rmpz_init_set_ui(1);

    my $t = Math::GMPz::Rmpz_init();
    my $w = Math::GMPz::Rmpz_init();
    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sqrt($x, $n);
    Math::GMPz::Rmpz_set($y, $x);

    Math::GMPz::Rmpz_add($w, $x, $x);
    Math::GMPz::Rmpz_set($r, $w);

    my $f2 = Math::GMPz::Rmpz_init_set($x);
    my $f1 = Math::GMPz::Rmpz_init_set_ui(1);

    foreach my $k (1 .. $reps) {

        # y = r*z - y
        Math::GMPz::Rmpz_mul($t, $r, $z);
        Math::GMPz::Rmpz_sub($y, $t, $y);

        # z = (n - y*y) / z
        Math::GMPz::Rmpz_mul($t, $y, $y);
        Math::GMPz::Rmpz_sub($t, $n, $t);
        Math::GMPz::Rmpz_divexact($z, $t, $z);

        # r = (x + y) / z
        Math::GMPz::Rmpz_add($t, $x, $y);

        # Floor division: floor((x+y)/z)
        # Math::GMPz::Rmpz_div($r, $t, $z);

        # Round (x+y)/z to nearest integer
        Math::GMPz::Rmpz_set($r, $z);
        Math::GMPz::Rmpz_addmul_ui($r, $t, 2);
        Math::GMPz::Rmpz_div($r, $r, $z);
        Math::GMPz::Rmpz_div_2exp($r, $r, 1);

        # f1 = (f1 + r*f2) % n
        Math::GMPz::Rmpz_addmul($f1, $f2, $r);
        Math::GMPz::Rmpz_mod($f1, $f1, $n);

        # swap f1 with f2
        ($f1, $f2) = ($f2, $f1);

        if (Math::GMPz::Rmpz_perfect_square_p($z)) {

            my $g = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_sqrt($g, $z);
            Math::GMPz::Rmpz_sub($g, $f1, $g);
            Math::GMPz::Rmpz_gcd($g, $g, $n);

            if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

                if (Math::GMPz::Rmpz_cmp($g, $n) == 0) {
                    return _array([_set_int($n)]);
                }

                my $x = Math::GMPz::Rmpz_init();
                my $y = Math::GMPz::Rmpz_init();

                Math::GMPz::Rmpz_set($y, $g);
                Math::GMPz::Rmpz_divexact($x, $n, $g);

                my @f = map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } ($x, $y);
                return _array(\@f);
            }
        }

        last if (Math::GMPz::Rmpz_cmpabs_ui($z, 1) == 0);
    }

    _array([_set_int($n)]);
}

sub mbe_factor {
    my ($n, $reps) = @_;

    $n = _any2mpz($$n) // return _array();

    Math::GMPz::Rmpz_cmp_ui($n, 1) > 0
      or return _array();

    $reps = defined($reps) ? do { ref($reps) eq __PACKAGE__ or _valid(\$reps); _any2ui($$reps) || return _array([_set_int($n)]) } : 10;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $g = Math::GMPz::Rmpz_init_nobless();

    state $A = Math::GMPz::Rmpz_init_nobless();
    state $B = Math::GMPz::Rmpz_init_nobless();
    state $C = Math::GMPz::Rmpz_init_nobless();

    my $n_str = Math::GMPz::Rmpz_get_str($n, 10);

    foreach my $k (1 .. $reps) {

        # Deterministic version
        #Math::GMPz::Rmpz_div_ui($t, $n, $k + 1);

        # Randomized version
        Math::GMPz::Rmpz_set_str($t, Math::Prime::Util::GMP::urandomm($n_str), 10);

        Math::GMPz::Rmpz_set($A, $t);
        Math::GMPz::Rmpz_set($B, $t);
        Math::GMPz::Rmpz_set_ui($C, 1);

        foreach my $i (0 .. Math::GMPz::Rmpz_sizeinbase($B, 2) - 1) {

            if (Math::GMPz::Rmpz_tstbit($B, $i)) {

                Math::GMPz::Rmpz_powm($C, $A, $C, $n);
                Math::GMPz::Rmpz_sub_ui($g, $C, 1);
                Math::GMPz::Rmpz_gcd($g, $g, $n);

                if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

                    if (Math::GMPz::Rmpz_cmp($g, $n) == 0) {
                        return _array([_set_int($n)]);
                    }

                    my $x = Math::GMPz::Rmpz_init();
                    my $y = Math::GMPz::Rmpz_init();

                    Math::GMPz::Rmpz_set($y, $g);
                    Math::GMPz::Rmpz_divexact($x, $n, $g);

                    my @f = map { bless \$_ } sort { Math::GMPz::Rmpz_cmp($a, $b) } ($x, $y);
                    return _array(\@f);
                }
            }

            Math::GMPz::Rmpz_powm($A, $A, $A, $n);
        }
    }

    _array([_set_int($n)]);
}

sub squfof_factor {
    my ($n, $k) = @_;
    (ref($k) eq __PACKAGE__ or _valid(\$k)) if defined($k);
    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::squfof_factor(_big2pistr($$n) // (return _array()), (defined($k) ? (_big2pistr($$k) || return _array($n)) : 1e4))
           ]
          );
}

sub ecm_factor {
    my ($n, $B1, $curves) = @_;

    (ref($B1) eq __PACKAGE__     or _valid(\$B1))     if defined($B1);
    (ref($curves) eq __PACKAGE__ or _valid(\$curves)) if defined($curves);

    _array(
           [map { _set_int($_) }
              Math::Prime::Util::GMP::ecm_factor(
                                                 _big2pistr($$n) // (return _array()),
                                                 (defined($B1)     ? (_big2pistr($$B1)     || return _array($n)) : ()),
                                                 (defined($curves) ? (_big2pistr($$curves) || return _array($n)) : ()),
                                                )
           ]
          );
}

sub qs_factor {
    my ($n) = @_;
    _array([map { _set_int($_) } Math::Prime::Util::GMP::qs_factor(_big2pistr($$n) // (return _array()))]);
}

sub dirichlet_convolution {
    my ($n, $f, $g) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    $f //= Sidef::Types::Block::Block->new(code => sub { ONE });
    $g //= Sidef::Types::Block::Block->new(code => sub { ONE });

    my @terms;
    my $result = ZERO;

    foreach my $d (_divisors($n)) {

        my $t =
          (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_init_set_ui($d)
          : Math::GMPz::Rmpz_init_set_str("$d", 10);

        my $u = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_divexact($u, $n, $t);

        $result = $result->add($f->run(bless \$t)->mul($g->run(bless \$u)));
    }

    return $result;
}

*dconv = \&dirichlet_convolution;

sub dirichlet_inverse {
    my ($n, $f) = @_;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my @D = map { _any2mpz($_) } _divisors($n);

    my @g;
    my $f1 = ${$f->run(ONE)};
    $g[0] = __inv__($f1);

    ref($g[0]) eq 'Math::MPFR' and goto &nan;
    ref($g[0]) eq 'Math::MPC'  and goto &nan;

    my $q   = Math::GMPz::Rmpz_init();
    my $sum = Math::GMPz::Rmpz_init();

    foreach my $i (1 .. $#D) {
        my $d = $D[$i];
        Math::GMPz::Rmpz_set_ui($sum, 0);

        foreach my $j (0 .. $i - 1) {
            my $c = $D[$j];

            if (Math::GMPz::Rmpz_divisible_p($d, $c)) {
                Math::GMPz::Rmpz_divexact($q, $d, $c);
                my $f_val = ${$f->run(_set_int($q))};
                $sum += $g[$j] * $f_val;
            }
        }

        $g[$i] = -$sum / $f1;
    }

    my $r = $g[-1];
    bless \$r;
}

*dinv = \&dirichlet_inverse;

sub moebius_inverse {
    my ($n, $f) = @_;

    $n = _big2uistr($$n) // goto &nan;

    my $q   = Math::GMPz::Rmpz_init();
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    my $native_n = (HAS_PRIME_UTIL and $n < ULONG_MAX);

    for my $d (map { $$_ } @{_set_int($n)->squarefree_divisors}) {

        $d = Math::GMPz::Rmpz_get_ui($d)
          if (ref($d) and Math::GMPz::Rmpz_fits_ulong_p($d));

        my $mu =
          (HAS_PRIME_UTIL and $d < ULONG_MAX)
          ? Math::Prime::Util::moebius($d)
          : Math::Prime::Util::GMP::moebius($d);

        my $q =
          $native_n
          ? Math::Prime::Util::divint($n, $d)
          : Math::Prime::Util::GMP::divint($n, $d);

        my $f_val = ${$f->run(_set_int($q))};

        if ($mu == 1) {
            $sum += $f_val;
        }
        else {
            $sum -= $f_val;
        }
    }

    bless \$sum;
}

*moebius_transform = \&moebius_inverse;

sub dirichlet_hyperbola {
    my ($n, $f, $g, $F, $G) = @_;

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    $f //= sub { 1 };
    $g //= sub { 1 };

    $F //= sub { $_[0] };
    $G //= sub { $_[0] };

    my $s = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sqrt($s, $n);
    Math::GMPz::Rmpz_fits_slong_p($s) || goto &nan;

    my $q_obj;
    my $native_n = Math::GMPz::Rmpz_fits_ulong_p($n);

    if ($native_n) {
        $n = Math::GMPz::Rmpz_get_ui($n);
    }
    else {
        $q_obj = bless \(my $value = Math::GMPz::Rmpz_init_set_ui(0));
    }

    my ($f_r, $g_r, $F_r, $G_r);
    my @terms;

    foreach my $k (1 .. Math::GMPz::Rmpz_get_ui($s)) {

        if ($native_n) {
            $q_obj = bless \(
                my $r =
                  HAS_PRIME_UTIL
                ? Math::Prime::Util::divint($n, $k)
                : Math::Prime::Util::GMP::divint($n, $k)

            );
        }
        else {
            Math::GMPz::Rmpz_div_ui($$q_obj, $n, $k);
        }

        my $k_obj = bless \$k;

        $f_r = $f->($k_obj);
        $g_r = $g->($k_obj);

        $f_r = $$f_r if ref($f_r);
        $g_r = $$g_r if ref($g_r);

        if (!ref($f_r) and $f_r == 0) {
            $G_r = 0;
            $F_r = $F->($q_obj);
        }
        elsif (!ref($g_r) and $g_r == 0) {
            $F_r = 0;
            $G_r = $G->($q_obj);
        }
        else {
            $F_r = $F->($q_obj);
            $G_r = $G->($q_obj);
        }

        $F_r = $$F_r if ref($F_r);
        $G_r = $$G_r if ref($G_r);

        push @terms,
          (
            (
             HAS_PRIME_UTIL ? Math::Prime::Util::mulint($f_r, $G_r)
             : Math::Prime::Util::GMP::mulint($f_r, $G_r)
            ),
            (
             HAS_PRIME_UTIL ? Math::Prime::Util::mulint($g_r, $F_r)
             : Math::Prime::Util::GMP::mulint($g_r, $F_r)
            )
          );
    }

    my $sum   = Math::Prime::Util::GMP::vecsum(@terms);
    my $s_obj = bless \$s;

    $F_r = $F->($s_obj);
    $G_r = $G->($s_obj);

    $F_r = $$F_r if ref($F_r);
    $G_r = $$G_r if ref($G_r);

    $sum = Math::Prime::Util::GMP::subint($sum, Math::Prime::Util::GMP::mulint($F_r, $G_r));
    _set_int($sum);
}

*dsum          = \&dirichlet_hyperbola;
*dirichlet_sum = \&dirichlet_hyperbola;

sub sum_remainders {
    my ($n, $v) = @_;

    # a(n,v) = Sum_{k=1..n} v % k

    ref($v) eq __PACKAGE__ or _valid(\$v);

    $n = _any2mpz($$n) // goto &nan;
    $v = _any2mpz($$v) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    my $S = sub {    # Sum_{k=1..n} sigma(k) = Sum_{k=1..n} k*floor(n/k)
        my ($n) = @_;

        if (Math::GMPz::Rmpz_sgn($n) < 0) {

            # Support for negative n:
            #   S(-n) = n*(n+1)/2 + S(n-1)

            # Based on the formula:
            # Sum_{k=1..n} -k*floor(n/-k) = Sum_{k=1..n} k*ceiling(n/k)
            #                             = n*(n+1)/2 + Sum_{k=1..n-1} sigma(k).

            $n = Math::GMPz::Rmpz_init_set($n);
            Math::GMPz::Rmpz_abs($n, $n);

            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_add_ui($t, $n, 1);
            Math::GMPz::Rmpz_mul($t, $t, $n);
            Math::GMPz::Rmpz_div_2exp($t, $t, 1);
            Math::GMPz::Rmpz_sub_ui($n, $n, 1);

            my $r = __SUB__->($n);
            Math::GMPz::Rmpz_add($t, $t, $r);
            return $t;
        }

        my $t = Math::GMPz::Rmpz_init();
        my $u = Math::GMPz::Rmpz_init();
        my $T = Math::GMPz::Rmpz_init_set_ui(0);

        my $s = Math::Prime::Util::GMP::sqrtint($n);

        foreach my $k (1 .. $s) {

            # T += faulhaber(idiv(n,k), 1) + k*idiv(n,k)
            Math::GMPz::Rmpz_div_ui($t, $n, $k);
            Math::GMPz::Rmpz_add_ui($u, $t, 1);
            Math::GMPz::Rmpz_mul($u, $u, $t);
            Math::GMPz::Rmpz_div_2exp($u, $u, 1);
            Math::GMPz::Rmpz_mul_ui($t, $t, $k);
            Math::GMPz::Rmpz_add($u, $u, $t);
            Math::GMPz::Rmpz_add($T, $T, $u);
        }

        # T -= faulhaber(s, 1)*s
        Math::GMPz::Rmpz_set_str($u, $s, 10);
        Math::GMPz::Rmpz_add_ui($t, $u, 1);
        Math::GMPz::Rmpz_mul($t, $t, $u);
        Math::GMPz::Rmpz_div_2exp($t, $t, 1);
        Math::GMPz::Rmpz_mul($t, $t, $u);
        Math::GMPz::Rmpz_sub($T, $T, $t);

        return $T;
    };

    my $G = sub {    # Sum_{k=A..B} k*floor(B/k)
        my ($A, $B) = @_;

        if (Math::GMPz::Rmpz_sgn($B) < 0) {

            # Support for negative B:
            #   G(a,-b) = T(b) - T(a-1) + G(a, b-1)
            # where T(n) = n*(n+1)/2

            $B = Math::GMPz::Rmpz_init_set($B);
            Math::GMPz::Rmpz_abs($B, $B);

            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_add_ui($t, $B, 1);
            Math::GMPz::Rmpz_mul($t, $t, $B);
            Math::GMPz::Rmpz_div_2exp($t, $t, 1);

            my $t2 = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_sub_ui($t2, $A, 1);
            Math::GMPz::Rmpz_mul($t2, $t2, $A);
            Math::GMPz::Rmpz_div_2exp($t2, $t2, 1);

            Math::GMPz::Rmpz_sub($t, $t, $t2);
            Math::GMPz::Rmpz_sub_ui($B, $B, 1);

            my $r = __SUB__->($A, $B);
            Math::GMPz::Rmpz_add($t, $t, $r);
            return $t;
        }

        my $t = Math::GMPz::Rmpz_init();
        my $u = Math::GMPz::Rmpz_init();
        my $v = Math::GMPz::Rmpz_init();
        my $w = Math::GMPz::Rmpz_init();

        my $T = Math::GMPz::Rmpz_init_set_ui(0);

        while (Math::GMPz::Rmpz_cmp($A, $B) <= 0) {

            Math::GMPz::Rmpz_div($t, $B, $A);
            Math::GMPz::Rmpz_div($u, $B, $t);

            # v = u*(u+1)/2
            Math::GMPz::Rmpz_add_ui($v, $u, 1);
            Math::GMPz::Rmpz_mul($v, $v, $u);
            Math::GMPz::Rmpz_div_2exp($v, $v, 1);

            # w = (a-1)*a/2
            Math::GMPz::Rmpz_sub_ui($w, $A, 1);
            Math::GMPz::Rmpz_mul($w, $w, $A);
            Math::GMPz::Rmpz_div_2exp($w, $w, 1);

            # T += t*(v - w)
            Math::GMPz::Rmpz_sub($v, $v, $w);
            Math::GMPz::Rmpz_mul($v, $v, $t);
            Math::GMPz::Rmpz_add($T, $T, $v);

            Math::GMPz::Rmpz_add_ui($A, $u, 1);
        }

        return $T;
    };

    # Optimization when n < sqrt(v)
    if (Math::GMPz::Rmpz_cmpabs($n * $n, $v) <= 0 and Math::GMPz::Rmpz_fits_ulong_p($n)) {

        my $sum = Math::GMPz::Rmpz_init_set_ui(0);

        $n = Math::GMPz::Rmpz_get_ui($n);

        if (Math::GMPz::Rmpz_fits_ulong_p($v)) {
            $v = Math::GMPz::Rmpz_get_ui($v);
            foreach my $k (1 .. $n) {
                Math::GMPz::Rmpz_add_ui($sum, $sum, $v % $k);
            }
        }
        else {
            state $t = Math::GMPz::Rmpz_init_nobless();
            foreach my $k (1 .. $n) {
                Math::GMPz::Rmpz_add_ui($sum, $sum, Math::GMPz::Rmpz_mod_ui($t, $v, $k));
            }
        }

        return bless \$sum;
    }

    # a(n,v) = n*v - S(v) + G(n+1, v)

    my $x = $S->($v);
    my $y = $G->($n + 1, $v);

    my $negative = 0;

    if (Math::GMPz::Rmpz_sgn($v) < 0) {
        $v = Math::GMPz::Rmpz_init_set($v);
        Math::GMPz::Rmpz_abs($v, $v);
        $negative = 1;
    }

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_mul($r, $n, $v);
    Math::GMPz::Rmpz_add($r, $r, $y);
    Math::GMPz::Rmpz_sub($r, $r, $x);

    if ($negative) {
        Math::GMPz::Rmpz_neg($r, $r);
    }

    bless \$r;
}

*sum_of_remainders = \&sum_remainders;

sub _divisors_bounded_native {
    my ($n_ui, $k_ui) = @_;

    my @d           = (1);
    my @prime_pairs = grep { $_->[0] <= $k_ui } _factor_exp($n_ui);

    foreach my $pp (@prime_pairs) {
        my ($p, $e) = @$pp;
        my @t;
        my $r = 1;

        for my $i (1 .. $e) {
            $r *= $p;
            foreach my $u (@d) {
                my $prod = $u * $r;
                push(@t, $prod) if ($prod <= $k_ui);
            }
        }
        push @d, @t;
    }

    _array([map { bless \$_ } sort { $a <=> $b } @d]);
}

sub _divisors_bounded_gmp {
    my ($n, $k) = @_;

    my @factors_exp;
    my $native_k = Math::GMPz::Rmpz_fits_ulong_p($k);
    my $k_ui     = ($native_k ? Math::GMPz::Rmpz_get_ui($k) : 0);

    # Gather factors into [prime, exponent] arrays
    if ($native_k and ($k_ui <= 1e6 or $k_ui == 1e7 or $k_ui == 1e8)) {
        my (undef, @flat_factors) = _primorial_trial_factor($n, $k_ui);
        my %counts;
        $counts{$_}++ for @flat_factors;
        @factors_exp = map { [$_, $counts{$_}] } keys %counts;
    }
    else {
        @factors_exp = grep { Math::Prime::Util::GMP::cmpint($_->[0], $k) <= 0 } _factor_exp($n);
    }

    return _array([ONE]) unless @factors_exp;

    my @d = (1);
    state $t     = Math::GMPz::Rmpz_init_nobless();
    state $r     = Math::GMPz::Rmpz_init_nobless();
    state $p_mpz = Math::GMPz::Rmpz_init_nobless();

    foreach my $pp (@factors_exp) {
        my ($p, $e) = @$pp;

        Math::GMPz::Rmpz_set_ui($r, 1);

        unless ($p < ULONG_MAX) {
            Math::GMPz::Rmpz_set_str($p_mpz, "$p", 10);
        }

        my @t;
        for my $i (1 .. $e) {

            if ($p < ULONG_MAX) {
                Math::GMPz::Rmpz_mul_ui($r, $r, $p);
            }
            else {
                Math::GMPz::Rmpz_mul($r, $r, $p_mpz);
            }

            foreach my $u (@d) {

                ref($u)
                  ? Math::GMPz::Rmpz_mul($t, $r, $u)
                  : Math::GMPz::Rmpz_mul_ui($t, $r, $u);

                if (Math::GMPz::Rmpz_cmp($t, $k) <= 0) {
                    my $val =
                      $native_k ? Math::GMPz::Rmpz_get_ui($t)
                      : (
                           Math::GMPz::Rmpz_fits_ulong_p($t) ? Math::GMPz::Rmpz_get_ui($t)
                         : Math::GMPz::Rmpz_init_set($t)
                        );
                    push(@t, $val);
                }
            }
        }
        push @d, @t;
    }

    _array([map { bless \$_ } sort { $a <=> $b } @d]);
}

sub _divisors_bounded {
    my ($n, $k_ref) = @_;

    ref($k_ref) eq __PACKAGE__ or _valid(\$k_ref);

    my $k = _any2mpz($$k_ref) // return _array();
    Math::GMPz::Rmpz_sgn($k) > 0 or return _array();

    # When k > n, cap k at n
    if (Math::GMPz::Rmpz_cmp($k, $n) > 0) {
        $k = Math::GMPz::Rmpz_init_set($n);
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, ULONG_MAX >> 1) < 0) {
        return _divisors_bounded_native(Math::GMPz::Rmpz_get_ui($n), Math::GMPz::Rmpz_get_ui($k));
    }

    _divisors_bounded_gmp($n, $k);
}

# Divisors d of n, such that d <= k, with k = n when `k` is not specified
sub divisors {
    my ($n, $k) = @_;
    $n = $$n;

    # Fast path: Unbounded, native integer
    if (FAST_MODE and !ref($n) and !defined($k)) {
        $n > 0 or return _array();
        my @divs =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divisors($n)
          : Math::Prime::Util::GMP::divisors($n);
        return _array([map { bless \$_ } @divs]);
    }

    # Validate and convert $n to GMPz
    $n = _any2mpz($n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    # Branch to Bounded divisors if $k is defined
    if (defined($k)) {
        return _divisors_bounded($n, $k);
    }

    # Fallback: unbounded
    _array([map { ($_ < ULONG_MAX) ? (bless \$_) : _set_int($_) } _divisors($n)]);
}

sub frobenius_number {
    my (@coins) = @_;
    _valid(\(@coins));

    my @A = map { _any2mpz($$_) // goto &nan } @coins;

    # Filter out zeros, negatives, and sort
    @A = sort { Math::GMPz::Rmpz_cmp($a, $b) } grep { Math::GMPz::Rmpz_sgn($_) > 0 } @A;

    goto &nan if @A < 1;

    # Frobenius number for a single coin 'a' is undefined (or -1 if a=1)
    if (@A == 1) {
        return MONE if Math::GMPz::Rmpz_cmp_ui($A[0], 1) == 0;
        goto &nan;
    }

    # Verify that the GCD of the entire set is exactly 1
    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set($g, $A[0]);
    for my $i (1 .. $#A) {
        Math::GMPz::Rmpz_gcd($g, $g, $A[$i]);
    }
    goto &nan if Math::GMPz::Rmpz_cmp_ui($g, 1) > 0;

    # O(1) Fast path for exactly 2 variables: a*b - a - b
    if (@A == 2) {
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_mul($r, $A[0], $A[1]);
        Math::GMPz::Rmpz_sub($r, $r, $A[0]);
        Math::GMPz::Rmpz_sub($r, $r, $A[1]);
        return bless \$r;
    }

    # SPFA algorithm over the residue classes of A[0]
    my $m =
        Math::GMPz::Rmpz_fits_ulong_p($A[0])
      ? Math::GMPz::Rmpz_get_ui($A[0])
      : die "[ERROR] frobenius_number: smallest denomination exceeds native unsigned long capacity.";

    my @d;
    my @in_q = (0) x $m;
    my @q    = (0);

    $d[0]    = Math::GMPz::Rmpz_init_set_ui(0);
    $in_q[0] = 1;

    state $new_dist = Math::GMPz::Rmpz_init_nobless();

    while (@q) {
        my $u = shift @q;
        $in_q[$u] = 0;

        for my $i (1 .. $#A) {
            my $w = $A[$i];

            # w_mod = w % m
            my $w_mod;
            if (Math::GMPz::Rmpz_fits_ulong_p($w)) {
                $w_mod = Math::GMPz::Rmpz_get_ui($w) % $m;
            }
            else {
                state $t_mod = Math::GMPz::Rmpz_init_nobless();
                Math::GMPz::Rmpz_mod_ui($t_mod, $w, $m);
                $w_mod = Math::GMPz::Rmpz_get_ui($t_mod);
            }

            my $v = ($u + $w_mod) % $m;
            Math::GMPz::Rmpz_add($new_dist, $d[$u], $w);

            if (!defined($d[$v]) or Math::GMPz::Rmpz_cmp($new_dist, $d[$v]) < 0) {
                $d[$v] = Math::GMPz::Rmpz_init_set($new_dist);
                if (!$in_q[$v]) {
                    push @q, $v;
                    $in_q[$v] = 1;
                }
            }
        }
    }

    # Frobenius number = max(d) - A[0]
    my $max_dist = Math::GMPz::Rmpz_init_set_ui(0);
    for my $i (0 .. $m - 1) {
        if (defined($d[$i]) and Math::GMPz::Rmpz_cmp($d[$i], $max_dist) > 0) {
            Math::GMPz::Rmpz_set($max_dist, $d[$i]);
        }
    }

    Math::GMPz::Rmpz_sub($max_dist, $max_dist, $A[0]);
    return bless \$max_dist;
}

sub partitions {
    my ($n, $max_value) = @_;

    $n = _any2ui($$n) // return _array();

    if (defined($max_value)) {
        ref($max_value) eq __PACKAGE__ or _valid(\$max_value);
        $max_value = _any2ui($$max_value) // return _array();
    }
    else {
        $max_value = $n;
    }

    my @results;
    my @path;

    sub {
        my ($n, $max_part) = @_;

        if ($n == 0) {
            unshift @results, _array([map { bless \$_ } @path]);
            return;
        }

        my $upper = ($n < $max_part ? $n : $max_part);

        for my $part (1 .. $upper) {
            push @path, $part;
            __SUB__->($n - $part, $part);
            pop @path;    # backtrack
        }
      }
      ->($n, $max_value);

    return _array(\@results);
}

sub strict_partitions {
    my ($n, $max_value) = @_;
    $n = _any2ui($$n) // return _array();
    if (defined($max_value)) {
        ref($max_value) eq __PACKAGE__ or _valid(\$max_value);
        $max_value = _any2ui($$max_value) // return _array();
    }
    else {
        $max_value = $n;
    }
    my @results;
    my @path;
    sub {
        my ($n, $max_part) = @_;
        if ($n == 0) {
            unshift @results, _array([@path]);
            return;
        }
        my $upper = ($n < $max_part ? $n : $max_part);
        for my $part (1 .. $upper) {
            push @path, bless \$part;
            __SUB__->($n - $part, $part - 1);
            pop @path;    # backtrack
        }
      }
      ->($n, $max_value);
    return _array(\@results);
}

sub multisets {
    my ($n, $k, $max_sum) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2ui($$n) // return _array();
    $k = _any2ui($$k) // return _array();

    if (defined($max_sum)) {
        ref($max_sum) eq __PACKAGE__ or _valid(\$max_sum);
        $max_sum = _any2ui($$max_sum) // return _array();
    }

    my @results;
    my @path;

    sub {
        my ($pos, $max_value, $sum) = @_;

        if ($pos == $n) {
            push @results, _array([@path]);
            return;
        }

        for my $v (1 .. $max_value) {
            if (defined $max_sum) {
                last if ($sum + $v > $max_sum);
            }
            push @path, bless \$v;
            __SUB__->($pos + 1, $v, $sum + $v);
            pop @path;    # backtrack
        }
      }
      ->(0, $k, 0);

    return _array(\@results);
}

sub multiplicative_partitions {
    my ($n, $max_value, $max_sum) = @_;

    # See also:
    #   https://oeis.org/A001055

    $n = _big2uistr($$n) // return _array();

    if (defined($max_value)) {
        ref($max_value) eq __PACKAGE__ or _valid(\$max_value);
        $max_value = _big2pistr($$max_value) // return _array();
    }

    if (defined($max_sum)) {
        ref($max_sum) eq __PACKAGE__ or _valid(\$max_sum);
        $max_sum = _big2pistr($$max_sum) // return _array();
    }

    my @divs = _divisors($n);
    CORE::shift(@divs);    # remove divisor '1'

    my @results;
    my $end       = $#divs;
    my @divs_objs = map { _set_int($_) } @divs;
    my @path;

    sub {
        my ($target, $min_idx, $curr_sum) = @_;

        if ($target == 1) {
            CORE::push(@results, _array([@path]));
            return;
        }

        for my $i ($min_idx .. $end) {
            my $d = $divs[$i];

            # Prune branch if the divisor exceeds the remaining target
            last if Math::Prime::Util::GMP::cmpint($d, $target) > 0;

            if (defined($max_value)) {
                last if Math::Prime::Util::GMP::cmpint($d, $max_value) > 0;
            }

            my $new_sum;
            if (defined($max_sum)) {
                $new_sum = Math::Prime::Util::GMP::addint($curr_sum, $d);
                last if Math::Prime::Util::GMP::cmpint($new_sum, $max_sum) > 0;
            }

            if (Math::Prime::Util::GMP::modint($target, $d) eq '0') {
                push @path, $divs_objs[$i];
                __SUB__->(Math::Prime::Util::GMP::divint($target, $d), $i, $new_sum);
                pop @path;    # backtrack
            }
        }
      }
      ->($n, 0, 0);

    @results = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, scalar(@$_)] } @results;

    return _array(\@results);
}

*mpartitions = \&multiplicative_partitions;

sub antidivisors {
    my ($n) = @_;

    $n = $$n;

    my @antidivisors;

    foreach my $d (@{(bless \$n)->divisors}) {
        my $y = __add__($$d, $$d);
        if (__cmp__($n, $y) > 0 and __cmp__(__mod__($n, $y), 0) != 0) {
            push @antidivisors, $y;
        }
    }

    my $n2 = __add__($n, $n);

    foreach my $d (@{(bless \__dec__($n2))->divisors}) {
        if (__cmp__($n, $$d) > 0 and __cmp__($$d, 2) >= 0 and __cmp__(__mod__($n, $$d), 0) != 0) {
            push @antidivisors, $$d;
        }
    }

    foreach my $d (@{(bless \__inc__($n2))->divisors}) {
        if (__cmp__($n, $$d) > 0 and __cmp__($$d, 2) >= 0 and __cmp__(__mod__($n, $$d), 0) != 0) {
            push @antidivisors, $$d;
        }
    }

    @antidivisors = sort { $a <=> $b } @antidivisors;
    _array([map { bless \$_ } @antidivisors]);
}

sub udivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my $pp = Math::GMPz::Rmpz_init();

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {
            ($e == 1)
              ? Math::GMPz::Rmpz_set_ui($pp, $p)
              : Math::GMPz::Rmpz_ui_pow_ui($pp, $p, $e);
        }
        else {
            Math::GMPz::Rmpz_set_str($pp, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($pp, $pp, $e) if ($e > 1);
        }

        my @t;

        foreach my $d (@d) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($t, $d, $pp);
            push @t, $t;
        }

        push @d, Math::GMPz::Rmpz_init_set($pp);
        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    unshift @d, ONE;

    _array(\@d);
}

*unitary_divisors = \&udivisors;

sub nudivisors {
    my ($n) = @_;
    ref($n) eq __PACKAGE__ or _valid(\$n);
    $n = _big2pistr($$n) // return _array();

    my @uni;
    my @non_uni;

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        my $p = Math::GMPz::Rmpz_init();
        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        # Pre-compute all powers of p: p^1, p^2, ..., p^e
        my @powers;
        my $curr = Math::GMPz::Rmpz_init_set($p);
        push @powers, $curr;
        for my $i (2 .. $e) {
            my $next_p = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($next_p, $powers[-1], $p);
            push @powers, $next_p;
        }

        # Elements carrying over by multiplying with p^0 (which is 1)
        my @next_uni     = map { Math::GMPz::Rmpz_init_set($_) } @uni;
        my @next_non_uni = map { Math::GMPz::Rmpz_init_set($_) } @non_uni;

        # 1. Handle intermediate (impure) powers: p^1 ... p^(e-1)
        # Any multiplication here transitions a combination into being non-unitary
        for my $i (1 .. $e - 1) {
            my $pw = $powers[$i - 1];

            # pw * 1 (implicit base)
            push @next_non_uni, Math::GMPz::Rmpz_init_set($pw);

            # pw * uni
            foreach my $d (@uni) {
                my $t = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($t, $pw, $d);
                push @next_non_uni, $t;
            }

            # pw * non_uni
            foreach my $d (@non_uni) {
                my $t = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($t, $pw, $d);
                push @next_non_uni, $t;
            }
        }

        # 2. Handle the full (pure) power: p^e
        # Multiplication here maintains the state (uni stays uni, non_uni stays non_uni)
        my $pw_e = $powers[$e - 1];

        # pw_e * 1 (implicit base)
        push @next_uni, Math::GMPz::Rmpz_init_set($pw_e);

        # pw_e * uni
        foreach my $d (@uni) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($t, $pw_e, $d);
            push @next_uni, $t;
        }

        # pw_e * non_uni
        foreach my $d (@non_uni) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($t, $pw_e, $d);
            push @next_non_uni, $t;
        }

        @uni     = @next_uni;
        @non_uni = @next_non_uni;
    }

    @non_uni = sort { Math::GMPz::Rmpz_cmp($a, $b) } @non_uni;
    @non_uni = map  { bless \$_ } @non_uni;

    _array(\@non_uni);
}

*non_unitary_divisors = \&nudivisors;

sub edivisors {    # OEIS: A322791
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @d     = (Math::GMPz::Rmpz_init_set_ui(1));
    my $r     = Math::GMPz::Rmpz_init();
    my $p_gmp = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        my $is_fast = (FAST_MODE and $p < ULONG_MAX);

        if (!$is_fast) {
            Math::GMPz::Rmpz_set_str($p_gmp, $p, 10);
        }

        my @t;
        foreach my $k (_divisors($e)) {

            if ($is_fast) {
                Math::GMPz::Rmpz_ui_pow_ui($r, $p, $k);
            }
            else {
                Math::GMPz::Rmpz_pow_ui($r, $p_gmp, $k);
            }

            foreach my $u (@d) {
                my $t = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($t, $u, $r);
                push @t, $t;
            }
        }

        @d = @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

*exponential_divisors = \&edivisors;

sub nedivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @exp = (Math::GMPz::Rmpz_init_set_ui(1));
    my @non_exp;

    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        # Save current states from previous prime stages
        my @curr_exp     = @exp;
        my @curr_non_exp = @non_exp;

        my @next_exp;
        my @next_non_exp;

        # Initialize r = p^0 = 1
        my $r = Math::GMPz::Rmpz_init_set_ui(1);

        for my $k (0 .. $e) {
            my $is_safe = ($k > 0 && $e % $k == 0);

            if ($is_safe) {

                # Safe exponent choice (Exponential)
                # Tracks maintain their current classification
                foreach my $u (@curr_exp) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @next_exp, $t;
                }
                foreach my $u (@curr_non_exp) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @next_non_exp, $t;
                }
            }
            else {
                # Forbidden exponent choice (Non-exponential)
                # Anything multiplied by this choice becomes non-exponential
                foreach my $u (@curr_exp) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @next_non_exp, $t;
                }
                foreach my $u (@curr_non_exp) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @next_non_exp, $t;
                }
            }

            # Progressively multiply to get the next power: r = r * p
            Math::GMPz::Rmpz_mul($r, $r, $p) if ($k < $e);
        }

        @exp     = @next_exp;
        @non_exp = @next_non_exp;
    }

    @non_exp = sort { Math::GMPz::Rmpz_cmp($a, $b) } @non_exp;
    @non_exp = map  { bless \$_ } @non_exp;

    _array(\@non_exp);
}

*non_exponential_divisors = \&nedivisors;

sub idivisors {    # OEIS: A077609
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @d = ($ONE);
    my $r = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        my @t;
        Math::GMPz::Rmpz_set($r, $p);

        foreach my $k (1 .. $e) {

            if (($e & $k) == $k) {
                foreach my $u (@d) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @t, $t;
                }
            }

            Math::GMPz::Rmpz_mul($r, $r, $p) if ($k < $e);
        }

        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

*infinitary_divisors = \&idivisors;

sub nidivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @inf = ($ONE);
    my @non_inf;

    my $r = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        # Save the current incoming states from previous prime factor stages
        my @curr_inf     = @inf;
        my @curr_non_inf = @non_inf;

        # Initialize next states with k = 0 (p^0 = 1 is always an infinitary choice)
        @inf     = map { Math::GMPz::Rmpz_init_set($_) } @curr_inf;
        @non_inf = map { Math::GMPz::Rmpz_init_set($_) } @curr_non_inf;

        Math::GMPz::Rmpz_set($r, $p);

        foreach my $k (1 .. $e) {

            if (($e & $k) == $k) {

                # Safe component choice (Infinitary)
                # Maintains the state classification of the multiplying factor
                foreach my $u (@curr_inf) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @inf, $t;
                }
                foreach my $u (@curr_non_inf) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_inf, $t;
                }
            }
            else {
                # Forbidden component choice (Non-infinitary)
                # Any factor multiplied by this choice transitions to non-infinitary
                foreach my $u (@curr_inf) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_inf, $t;
                }
                foreach my $u (@curr_non_inf) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_inf, $t;
                }
            }

            Math::GMPz::Rmpz_mul($r, $r, $p) if ($k < $e);
        }
    }

    @non_inf = sort { Math::GMPz::Rmpz_cmp($a, $b) } @non_inf;
    @non_inf = map  { bless \$_ } @non_inf;

    _array(\@non_inf);
}

*non_infinitary_divisors = \&nidivisors;

sub biudivisors {    # OEIS: A222266
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    my @d = ($ONE);
    my $r = Math::GMPz::Rmpz_init();
    my $w = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        my @t;
        Math::GMPz::Rmpz_set($r, $p);

        foreach my $k (1 .. $e) {

            Math::GMPz::Rmpz_divexact($w, $n, $r);

            if ((bless \$w)->gcud(bless \$r)->is_one) {
                foreach my $u (@d) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @t, $t;
                }
            }

            Math::GMPz::Rmpz_mul($r, $r, $p) if ($k < $e);
        }

        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

*bdivisors           = \&biudivisors;
*bi_unitary_divisors = \&biudivisors;

sub nbdivisors {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    my @biu = ($ONE);
    my @non_biu;

    my $r = Math::GMPz::Rmpz_init();
    my $w = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        # Save the current incoming states for this prime factor stage
        my @curr_biu     = @biu;
        my @curr_non_biu = @non_biu;

        # Initialize next states with k = 0 (equivalent to multiplying by p^0 = 1)
        @biu     = map { Math::GMPz::Rmpz_init_set($_) } @curr_biu;
        @non_biu = map { Math::GMPz::Rmpz_init_set($_) } @curr_non_biu;

        Math::GMPz::Rmpz_set($r, $p);

        foreach my $k (1 .. $e) {
            Math::GMPz::Rmpz_divexact($w, $n, $r);

            if ((bless \$w)->gcud(bless \$r)->is_one) {

                # Safe component choice (Bi-unitary)
                # Maintains the state classification of the multiplying factor
                foreach my $u (@curr_biu) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @biu, $t;
                }
                foreach my $u (@curr_non_biu) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_biu, $t;
                }
            }
            else {
                # Forbidden component choice (Non-bi-unitary)
                # Any factor multiplied by this choice becomes non-bi-unitary
                foreach my $u (@curr_biu) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_biu, $t;
                }
                foreach my $u (@curr_non_biu) {
                    my $t = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_mul($t, $u, $r);
                    push @non_biu, $t;
                }
            }

            Math::GMPz::Rmpz_mul($r, $r, $p) if ($k < $e);
        }
    }

    @non_biu = sort { Math::GMPz::Rmpz_cmp($a, $b) } @non_biu;
    @non_biu = map  { bless \$_ } @non_biu;

    _array(\@non_biu);
}

*non_bi_unitary_divisors = \&nbdivisors;
*nbiudivisors            = \&nbdivisors;

sub prime_power_divisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my $u = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        push @d, (($q < ULONG_MAX) ? $q : Math::GMPz::Rmpz_init_set($p));
        next if ($e == 1);

        Math::GMPz::Rmpz_set($u, $p);

        foreach my $i (2 .. $e) {
            Math::GMPz::Rmpz_mul($u, $u, $p);
            push @d, Math::GMPz::Rmpz_init_set($u);
        }
    }

    @d = sort { $a <=> $b } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

sub prime_power_udivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        my $pp = Math::GMPz::Rmpz_init();

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($pp, $p, $e);
        }
        else {
            Math::GMPz::Rmpz_set_str($pp, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($pp, $pp, $e) if ($e > 1);
        }

        push @d, $pp;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

*prime_power_unitary_divisors = \&prime_power_udivisors;
*unitary_prime_power_divisors = \&prime_power_udivisors;

sub powerfree_divisors {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    if ($k == 1) {
        return _array([ONE]);
    }

    state $r = Math::GMPz::Rmpz_init_nobless();

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        $p =
          (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_init_set_ui($p)
          : Math::GMPz::Rmpz_init_set_str("$p", 10);

        if ($k <= $e) {
            $e = $k - 1;
        }

        Math::GMPz::Rmpz_set($r, $p);

        my @t;
        foreach my $i (1 .. $e) {
            foreach my $d (@d) {
                my $t = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($t, $r, $d);
                push @t, $t;
            }
            push @t, Math::GMPz::Rmpz_init_set($r);
            Math::GMPz::Rmpz_mul($r, $r, $p) if ($i < $e);
        }

        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    unshift @d, ONE;

    _array(\@d);
}

sub squarefree_divisors {
    (TWO)->powerfree_divisors($_[0]);
}

sub cubefree_divisors {
    (THREE)->powerfree_divisors($_[0]);
}

sub powerful_divisors {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $p = Math::GMPz::Rmpz_init_nobless();

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        next if ($e < $k);    # cannot be k-powerful if e < k

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        Math::GMPz::Rmpz_pow_ui($r, $p, $k);

        my @t;
        my $lim = $e - $k + 1;
        foreach my $i (1 .. $lim) {
            foreach my $d (@d) {
                my $t = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($t, $r, $d);
                push @t, $t;
            }
            push @t, Math::GMPz::Rmpz_init_set($r);
            Math::GMPz::Rmpz_mul($r, $r, $p) if ($i < $lim);
        }
        push @d, @t;
    }
    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;
    unshift @d, ONE;
    _array(\@d);
}

sub squarefull_divisors {
    (TWO)->powerful_divisors($_[0]);
}

sub cubefull_divisors {
    (THREE)->powerful_divisors($_[0]);
}

sub powerful_udivisors {
    my ($k, $n) = @_;
    ref($n) eq __PACKAGE__ or _valid(\$n);
    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $p = Math::GMPz::Rmpz_init_nobless();
    my @d;

    foreach my $pe (_factor_exp($n)) {
        my ($q, $e) = @$pe;

        next if ($e < $k);    # cannot be k-powerful if e < k

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, "$q", 10);

        Math::GMPz::Rmpz_pow_ui($r, $p, $e);

        my @t;
        foreach my $d (@d) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($t, $r, $d);
            push @t, $t;
        }
        push @t, Math::GMPz::Rmpz_init_set($r);
        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;
    unshift @d, ONE;

    _array(\@d);
}

*unitary_powerful_divisors = \&powerful_udivisors;
*powerful_unitary_divisors = \&powerful_udivisors;

sub squarefull_udivisors {
    (TWO)->powerful_udivisors($_[0]);
}

*squarefull_unitary_divisors = \&squarefull_udivisors;
*unitary_squarefull_divisors = \&squarefull_udivisors;

sub cubefull_udivisors {
    (THREE)->powerful_udivisors($_[0]);
}

*cubefull_unitary_divisors = \&cubefull_udivisors;
*unitary_cubefull_divisors = \&cubefull_udivisors;

sub _power_divisors_func {
    my ($k, $factor_exp) = @_;

    my @d = ($ONE);
    my $r = Math::GMPz::Rmpz_init();
    my $p = Math::GMPz::Rmpz_init();

    foreach my $pe (grep { $_->[1] >= $k } @$factor_exp) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $q)
          : Math::GMPz::Rmpz_set_str($p, $q, 10);

        my @t;
        for (my $i = $k ; $i <= $e ; $i += $k) {

            Math::GMPz::Rmpz_pow_ui($r, $p, $i);

            foreach my $d (@d) {
                my $z = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul($z, $r, $d);
                push @t, $z;
            }
        }

        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
}

sub power_divisors {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    _power_divisors_func($k, [_factor_exp($n)]);
}

sub perfect_power_divisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @lists;
    my @factor_exp = _factor_exp($n);
    my $max_k      = List::Util::max(map { $_->[1] } @factor_exp);

    if (!defined($max_k) or $max_k == 1) {
        return _array([ONE]);
    }

    foreach my $k (2 .. $max_k) {
        push @lists, _power_divisors_func($k, \@factor_exp);
    }

    _array([map { @$_ } @lists])->isort->iuniq;
}

*pp_divisors = \&perfect_power_divisors;

sub square_divisors {
    (TWO)->power_divisors($_[0]);
}

sub cube_divisors {
    (THREE)->power_divisors($_[0]);
}

my $power_udivisors_func = sub {
    my ($k, $factor_exp) = @_;

    my $pp = Math::GMPz::Rmpz_init();

    my @d = ($ONE);
    foreach my $pe (grep { $_->[1] % $k == 0 } @$factor_exp) {
        my ($q, $e) = @$pe;

        (FAST_MODE and $q < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($pp, $q)
          : Math::GMPz::Rmpz_set_str($pp, $q, 10);

        if ($e == 2) {
            Math::GMPz::Rmpz_mul($pp, $pp, $pp);
        }
        else {
            Math::GMPz::Rmpz_pow_ui($pp, $pp, $e);
        }

        my @t;
        foreach my $d (@d) {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($z, $pp, $d);
            push @t, $z;
        }
        push @d, @t;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    _array(\@d);
};

sub power_udivisors {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    return $power_udivisors_func->($k, [_factor_exp($n)]);
}

*unitary_power_divisors = \&power_udivisors;
*power_unitary_divisors = \&power_udivisors;

sub perfect_power_udivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @lists;
    my @factor_exp = _factor_exp($n);
    my $max_k      = List::Util::max(map { $_->[1] } @factor_exp);

    if (!defined($max_k) or $max_k == 1) {
        return _array([ONE]);
    }

    foreach my $k (2 .. $max_k) {
        push @lists, $power_udivisors_func->($k, \@factor_exp);
    }

    _array([map { @$_ } @lists])->isort->iuniq;
}

*pp_udivisors                   = \&perfect_power_udivisors;
*perfect_power_unitary_divisors = \&perfect_power_udivisors;

sub square_udivisors {
    (TWO)->power_udivisors($_[0]);
}

sub cube_udivisors {
    (THREE)->power_udivisors($_[0]);
}

sub powerfree_udivisors {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _big2pistr($$n) // return _array();
    $k = _any2ui($$k) || return _array();

    if ($k == 1) {
        return _array([ONE]);
    }

    my @d;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        $e < $k or next;

        my $r = Math::GMPz::Rmpz_init();

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($r, $p, $e);
        }
        else {
            Math::GMPz::Rmpz_set_str($r, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($r, $r, $e);
        }

        my @tmp = ($r);

        foreach my $d (@d) {
            my $t = Math::GMPz::Rmpz_init();
            Math::GMPz::Rmpz_mul($t, $d, $r);
            push @tmp, $t;
        }

        push @d, @tmp;
    }

    @d = sort { Math::GMPz::Rmpz_cmp($a, $b) } @d;
    @d = map  { bless \$_ } @d;

    unshift @d, ONE;

    _array(\@d);
}

sub squarefree_udivisors {
    (TWO)->powerfree_udivisors($_[0]);
}

sub cubefree_udivisors {
    (THREE)->powerfree_udivisors($_[0]);
}

sub prime_divisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @d;
    foreach my $pk (_factor_exp($n)) {
        my $p = $pk->[0];
        if (FAST_MODE and $p < ULONG_MAX) {
            push @d, bless \$p;
        }
        else {
            $p = Math::GMPz::Rmpz_init_set_str("$p", 10);
            push @d, bless \$p;
        }
    }

    _array(\@d);
}

sub prime_udivisors {
    my ($n) = @_;

    $n = _big2pistr($$n) // return _array();

    my @d;
    foreach my $pk (_factor_exp($n)) {
        if ($pk->[1] == 1) {
            my $p = $pk->[0];
            push @d, _set_int($p);
        }
    }

    _array(\@d);
}

*unitary_prime_divisors = \&prime_udivisors;
*prime_unitary_divisors = \&prime_udivisors;

sub exp_mangoldt {
    my ($n) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {    # native integer

        $n < 0 and goto &nan;

        if (HAS_PRIME_UTIL) {
            $n || return ZERO;
            $n = Math::Prime::Util::exp_mangoldt($n);
            $n == 1 and return ONE;
            return bless \$n;
        }
    }

    $n || return ZERO;
    $n = Math::Prime::Util::GMP::exp_mangoldt($n);
    $n eq '1' and return ONE;
    _set_int($n);
}

sub mangoldt {
    $_[0]->exp_mangoldt->log;
}

sub exp_mangoldt_sum {
    my ($n) = @_;

    # Formula:
    #   exp_mangoldt_sum(n) = n - prime_power_count(n) + Sum_{k=1..floor(log_2(n))} prime_sum(floor(n^(1/k)))

    my $z     = _any2mpz($$n);
    my $t     = Math::GMPz::Rmpz_init();
    my @terms = $n->prime_power_count->neg;
    my $t_obj = bless \$t;

    foreach my $k (1 .. Math::GMPz::Rmpz_sizeinbase($z, 2) - 1) {
        Math::GMPz::Rmpz_root($t, $z, $k);
        push @terms, $t_obj->prime_sum;
    }

    Sidef::Types::Number::Number::sum($n, @terms);
}

sub primitive_part {
    my ($n, $f) = @_;
    $f // return $n->exp_mangoldt;

    my $z = _any2mpz($$n) // goto &nan;

    my (@u, @v);

    foreach my $d (@{$n->squarefree_divisors}) {
        my $t = Math::GMPz::Rmpz_init();

        ref($$d)
          ? Math::GMPz::Rmpz_divexact($t, $z, $$d)
          : Math::GMPz::Rmpz_divexact_ui($t, $z, $$d);

        my $r = $f->run(bless \$t);
        my $m = Math::Prime::Util::GMP::moebius($$d);

        ($m == 1) ? push(@u, $$r) : push(@v, $$r);
    }

    my $u = @u ? _binsplit(\@u, \&__mul__) : $ONE;
    my $v = @v ? _binsplit(\@v, \&__mul__) : $ONE;

    bless \__div__($u, $v);
}

sub euler_phi {
    my ($n, $k) = @_;

    if (defined($k)) {
        goto &jordan_totient;
    }

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::euler_phi($n)
          : Math::Prime::Util::GMP::totient($n);
        return bless \$r;
    }

    $n = _big2uistr($n) // goto &nan;

    if (CORE::length($n) < SPECIAL_FACTORS_MIN) {
        return _set_int(Math::Prime::Util::GMP::totient($n));
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pp (_factor_exp($n)) {
        my ($p, $e) = @$pp;

        # Multiplicative with a(p^e) = (p - 1)*p^(e-1)

        if (FAST_MODE and $p < ULONG_MAX) {
            push @terms, $p - 1;

            if ($e >= 2) {

                if ($e == 2) {
                    push @terms, $p;
                    next;
                }

                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e - 1);
                push @terms,
                  (
                      Math::GMPz::Rmpz_fits_ulong_p($t)
                    ? Math::GMPz::Rmpz_get_ui($t)
                    : Math::GMPz::Rmpz_init_set($t)
                  );
            }
        }
        else {

            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_sub_ui($t, $t, 1);
            push @terms, Math::GMPz::Rmpz_init_set($t);

            if ($e >= 2) {
                Math::GMPz::Rmpz_add_ui($t, $t, 1);
                Math::GMPz::Rmpz_pow_ui($t, $t, $e - 1) if ($e != 2);
                push @terms, Math::GMPz::Rmpz_init_set($t);
            }
        }
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

*EulerPhi      = \&euler_phi;
*eulerphi      = \&euler_phi;
*euler_totient = \&euler_phi;
*totient       = \&euler_phi;

sub phi_sum {
    my ($n, $k) = @_;

    # TODO: use a faster formula
    # https://github.com/trizen/perl-scripts/blob/master/Math/partial_sums_of_euler_totient_function_fast.pl

    # TODO: generalize to compute: Sum_{k=1..n} k^m * J_i(k)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if (HAS_PRIME_UTIL and $k == 1) {
        $n = _big2uistr($$n) // return ZERO;
        return _set_int(Math::Prime::Util::sumtotient($n));
    }

    my $k_obj = bless \$k;

    my $f = sub { $_[0]->ipow($k_obj) };
    my $g = sub { $_[0]->moebius };

    my $F = sub { _set_int(Math::Prime::Util::GMP::powersum(${$_[0]}, $k)) };
    my $G = sub { $_[0]->mertens };

    if ($k == 1) {
        $f = sub { $_[0] };
    }
    elsif ($k == 2) {
        $f = sub { $_[0]->sqr };
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

*totient_sum        = \&phi_sum;
*euler_phi_sum      = \&phi_sum;
*jordan_totient_sum = \&phi_sum;

sub cototient {
    my ($n, $k) = @_;

    if ($n->is_zero) {
        return ZERO;
    }

    if (!defined($k)) {
        return $n->sub($n->euler_phi);
    }

    $k //= ONE;
    $n->ipow($k)->sub($n->jordan_totient($k));
}

sub cototient_sum {
    my ($n, $k) = @_;
    $k //= ONE;
    $n->faulhaber_sum($k)->sub($n->phi_sum($k));
}

sub _n_over_d_divisors {
    my ($N, $d, $u, $D) = @_;

    # N = a positive integer
    # d = a divisor of N
    # u = temporary Math::GMPz object
    # D = array ref with divisors of N, assumed sorted ascending

    Math::GMPz::Rmpz_divexact($u, $N, $d);

    # When u = N/d is a native integer, call Math::Prime::Util::divisors().
    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($u)) {
        return Math::Prime::Util::divisors(Math::GMPz::Rmpz_get_ui($u));
    }

    # When N has too many divisors, it's faster
    # to simply compute the divisors of u = N/d
    if (scalar(@$D) > 1e4) {
        return _divisors($u);
    }

    my $d_str = Math::GMPz::Rmpz_get_str($d, 10);
    my $u_str = Math::GMPz::Rmpz_get_str($u, 10);

    state $limit = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sqrt($u, $u);
    Math::GMPz::Rmpz_mul($limit, $u, $d);

    my (@front, @back);

    foreach my $k (@$D) {

        if (FAST_MODE and $k < ULONG_MAX) {
            last if Math::GMPz::Rmpz_cmp_ui($limit, $k) < 0;
            Math::GMPz::Rmpz_gcd_ui($u, $d, $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($u, $k, 10);
            last if Math::GMPz::Rmpz_cmp($limit, $u) < 0;
            Math::GMPz::Rmpz_gcd($u, $u, $d);
        }

        if (Math::GMPz::Rmpz_cmp($u, $d) == 0) {

            # We found x. Immediately calculate its pair y.
            my $x = Math::Prime::Util::GMP::divint($k,     $d_str);
            my $y = Math::Prime::Util::GMP::divint($u_str, $x);

            push @front, $x;
            unshift @back, $y if $x ne $y;
        }
    }

    # Return combined halves to naturally maintain the ascending sorted order
    return (@front, @back);
}

sub _dynamic_preimage {
    my ($N, $L, $D, %opt) = @_;

    # Based on invphi.gp ver. 2.1 by Max Alekseyev.

    # With memory optimization by Dana Jacobsen
    # https://github.com/danaj/Math-Prime-Util/issues/83

    # Phase 1: Determine which intermediate values are actually needed
    my %needed = (Math::GMPz::Rmpz_get_str($N, 10) => undef);
    my @operations;

    my $u       = Math::GMPz::Rmpz_init();
    my $unitary = $opt{unitary};

    foreach my $l (@$L) {
        my @current_ops;

        foreach my $pair (@$l) {
            my ($x, $y) = @$pair;

            foreach my $d (_n_over_d_divisors($N, $x, $u, $D)) {

                (FAST_MODE and $d < ULONG_MAX)
                  ? Math::GMPz::Rmpz_mul_ui($u, $x, $d)
                  : do {
                    Math::GMPz::Rmpz_set_str($u, $d, 10);
                    Math::GMPz::Rmpz_mul($u, $u, $x);
                  };

                # Only track operations that lead to needed values
                if (exists $needed{Math::GMPz::Rmpz_get_str($u, 10)}) {
                    undef $needed{$d};
                    push(@current_ops, [$d, $y, Math::GMPz::Rmpz_init_set($u)]);
                }
            }
        }

        unshift(@operations, \@current_ops) if @current_ops;
    }

    undef %needed;

    my %R = (1 => [1]);

    foreach my $ops (@operations) {
        my %t;

        foreach my $op (@$ops) {
            my ($d, $y, $F) = @$op;

            if (exists $R{$d}) {

                my $key  = Math::GMPz::Rmpz_get_str($F, 10);
                my @list = @{$R{$d}};

                if ($unitary) {
                    @list = grep {
                        ref($_)
                          ? do {
                            Math::GMPz::Rmpz_gcd($u, $y, $_);
                            Math::GMPz::Rmpz_cmp_ui($u, 1) == 0;
                          }
                          : do {
                            Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $y, $_) == 1;
                          }
                    } @list;
                }

                push @{$t{$key}}, map {
                    my $w = Math::GMPz::Rmpz_init();
                    ref($_) ? Math::GMPz::Rmpz_mul($w, $y, $_) : Math::GMPz::Rmpz_mul_ui($w, $y, $_);
                    $w = Math::GMPz::Rmpz_get_ui($w) if Math::GMPz::Rmpz_fits_ulong_p($w);
                    $w;
                } @list;
            }
        }

        foreach my $k (keys %t) {
            push @{$R{$k}}, @{delete $t{$k}};
        }
    }

    $R{$N} // [];
}

sub _dynamic_preimage_minmax {
    my ($N, $L, $D, %opt) = @_;

    # Based on invphi.gp ver. 2.1 by Max Alekseyev.

    my %R = (1 => $ONE);

    my $u = Math::GMPz::Rmpz_init();
    my $w = Math::GMPz::Rmpz_init();

    my $min = $opt{min};

    foreach my $l (@$L) {
        my %t;

        foreach my $pair (@$l) {
            my ($x, $y) = @$pair;

            foreach my $d (_n_over_d_divisors($N, $x, $u, $D)) {
                if (exists $R{$d}) {

                    (FAST_MODE and $d < ULONG_MAX)
                      ? Math::GMPz::Rmpz_mul_ui($u, $x, $d)
                      : do {
                        Math::GMPz::Rmpz_set_str($u, $d, 10);
                        Math::GMPz::Rmpz_mul($u, $u, $x);
                      };

                    my $key = Math::GMPz::Rmpz_get_str($u, 10);

                    Math::GMPz::Rmpz_mul($w, $y, $R{$d});

                    if (
                        !exists($t{$key})
                        or (
                            $min
                            ? (Math::GMPz::Rmpz_cmp($w, $t{$key}) < 0)
                            : (Math::GMPz::Rmpz_cmp($w, $t{$key}) > 0)
                           )
                      ) {
                        $t{$key} = Math::GMPz::Rmpz_init_set($w);
                    }
                }
            }
        }

        while (my ($key, $value) = each %t) {
            if (
                !exists($R{$key})
                or (
                    $min
                    ? (Math::GMPz::Rmpz_cmp($value, $R{$key}) < 0)
                    : (Math::GMPz::Rmpz_cmp($value, $R{$key}) > 0)
                   )
              ) {
                $R{$key} = $value;
            }
        }
    }

    $R{$N};
}

sub _dynamic_preimage_len_bigint {
    my ($N, $L, $D) = @_;

    # Based on invphi.gp ver. 2.1 by Max Alekseyev.

    my %R = (1 => Math::GMPz::Rmpz_init_set_ui(1));
    my $u = Math::GMPz::Rmpz_init();

    foreach my $l (@$L) {
        my %t;

        foreach my $pair (@$l) {

            my $x = $pair->[0];

            foreach my $d (_n_over_d_divisors($N, $x, $u, $D)) {
                if (exists $R{$d}) {

                    (FAST_MODE and $d < ULONG_MAX)
                      ? Math::GMPz::Rmpz_mul_ui($u, $x, $d)
                      : do {
                        Math::GMPz::Rmpz_set_str($u, $d, 10);
                        Math::GMPz::Rmpz_mul($u, $u, $x);
                      };

                    my $key = Math::GMPz::Rmpz_get_str($u, 10);

                    if (!exists $t{$key}) {
                        $t{$key} = Math::GMPz::Rmpz_init_set_ui(0);
                    }

                    Math::GMPz::Rmpz_add($t{$key}, $t{$key}, $R{$d});
                }
            }
        }

        while (my ($key, $value) = each %t) {
            if (!exists $R{$key}) {
                $R{$key} = Math::GMPz::Rmpz_init_set_ui(0);
            }
            Math::GMPz::Rmpz_add($R{$key}, $R{$key}, $value);
        }
    }

    if (exists $R{$N}) {
        return Math::GMPz::Rmpz_get_str($R{$N}, 10);
    }

    return 0;
}

sub _dynamic_preimage_len {
    my ($N, $L, $D) = @_;

    # Based on invphi.gp ver. 2.1 by Max Alekseyev.

    my %R = (1 => 1);
    my $u = Math::GMPz::Rmpz_init();

    foreach my $l (@$L) {
        my %t;

        foreach my $pair (@$l) {

            my $x = $pair->[0];

            foreach my $d (_n_over_d_divisors($N, $x, $u, $D)) {
                if (exists $R{$d}) {

                    (FAST_MODE and $d < ULONG_MAX)
                      ? Math::GMPz::Rmpz_mul_ui($u, $x, $d)
                      : do {
                        Math::GMPz::Rmpz_set_str($u, $d, 10);
                        Math::GMPz::Rmpz_mul($u, $u, $x);
                      };

                    $t{Math::GMPz::Rmpz_get_str($u, 10)} += $R{$d};
                }
            }
        }

        while (my ($key, $value) = each %t) {
            $R{$key} += $value;
        }
    }

    my $r = $R{$N} // 0;
    ($r < ~0) || goto &_dynamic_preimage_len_bigint;
    return $r;
}

sub _cook_euler_phi {
    my ($N) = @_;

    my $p = Math::GMPz::Rmpz_init();
    my $v = Math::GMPz::Rmpz_init();

    my %L;
    my @D = _divisors($N);

    foreach my $d (@D) {

        Math::Prime::Util::GMP::is_prime(Math::Prime::Util::GMP::addint($d, 1)) || next;

        (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $d)
          : Math::GMPz::Rmpz_set_str($p, $d, 10);

        Math::GMPz::Rmpz_add_ui($p, $p, 1);

        my $t = Math::GMPz::Rmpz_remove($v, $N, $p);

        push @{$L{$p}}, map {

            # [(p-1)*p^(k-1), p^k]

            my $x = Math::GMPz::Rmpz_init();
            my $y = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_pow_ui($v, $p, $_ - 1);
            Math::GMPz::Rmpz_pow_ui($y, $p, $_);
            Math::GMPz::Rmpz_sub_ui($x, $p, 1);
            Math::GMPz::Rmpz_mul($x, $x, $v);

            [$x, $y]
        } 1 .. $t + 1;
    }

    ([values %L], \@D);
}

sub inverse_totient {
    my ($n) = @_;

    # Algorithm "invphi" from invphi.gp ver. 2.1 by Max Alekseyev.

    $n = _any2mpz($$n) // return _array();

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return _array(ZERO) if !Math::GMPz::Rmpz_sgn($n);
        return _array();
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {    # XXX: MPU 0.73 leaks memory
        return _array([map { bless \$_ } Math::Prime::Util::inverse_totient(Math::GMPz::Rmpz_get_ui($n))]);
    }

    my $result = _dynamic_preimage($n, _cook_euler_phi($n));
    _array([map { bless \$_ } sort { $a <=> $b } @$result]);
}

*inverse_phi       = \&inverse_totient;
*phi_inverse       = \&inverse_totient;
*inverse_euler_phi = \&inverse_totient;

sub inverse_totient_len {
    my ($n) = @_;

    $n = _any2mpz($$n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ONE if !Math::GMPz::Rmpz_sgn($n);
        return ZERO;
    }

    my $r;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $r = scalar Math::Prime::Util::inverse_totient(Math::GMPz::Rmpz_get_ui($n));
    }
    else {
        $r = _dynamic_preimage_len($n, _cook_euler_phi($n));
    }

    _set_int($r);
}

*phi_inverse_len       = \&inverse_totient_len;
*inverse_phi_len       = \&inverse_totient_len;
*inverse_euler_phi_len = \&inverse_totient_len;

sub inverse_euler_phi_min {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_euler_phi($n), min => 1) // return undef;
    bless \$r;
}

*phi_inverse_min     = \&inverse_euler_phi_min;
*inverse_phi_min     = \&inverse_euler_phi_min;
*inverse_totient_min = \&inverse_euler_phi_min;

sub inverse_euler_phi_max {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_euler_phi($n), min => 0) // return undef;
    bless \$r;
}

*phi_inverse_max     = \&inverse_euler_phi_max;
*inverse_phi_max     = \&inverse_euler_phi_max;
*inverse_totient_max = \&inverse_euler_phi_max;

sub _cook_dedekind_psi {
    my ($N, $k) = @_;

    my $p = Math::GMPz::Rmpz_init();
    my $v = Math::GMPz::Rmpz_init();

    my %L;
    my @D = _divisors($N);

    foreach my $d (@D) {

        Math::Prime::Util::GMP::is_prime(Math::Prime::Util::GMP::subint($d, 1)) || next;

        (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($p, $d)
          : Math::GMPz::Rmpz_set_str($p, $d, 10);

        Math::GMPz::Rmpz_sub_ui($p, $p, 1);

        my $t = Math::GMPz::Rmpz_remove($v, $N, $p);

        push @{$L{$p}}, map {

            # [(p+1)*p^(k-1), p^k]

            my $x = Math::GMPz::Rmpz_init();
            my $y = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_pow_ui($v, $p, $_ - 1);
            Math::GMPz::Rmpz_pow_ui($y, $p, $_);

            Math::GMPz::Rmpz_add_ui($x, $p, 1);
            Math::GMPz::Rmpz_mul($x, $x, $v);

            [$x, $y]
        } 1 .. $t + 1;
    }

    ([values %L], \@D);
}

sub inverse_dedekind_psi {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return _array(ZERO) if !Math::GMPz::Rmpz_sgn($n);
        return _array();
    }

    my $result = _dynamic_preimage($n, _cook_dedekind_psi($n));
    _array([map { bless \$_ } sort { $a <=> $b } @$result]);
}

*psi_inverse = \&inverse_dedekind_psi;
*inverse_psi = \&inverse_dedekind_psi;

sub inverse_dedekind_psi_len {
    my ($n) = @_;

    $n = _any2mpz($$n) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ONE if !Math::GMPz::Rmpz_sgn($n);
        return ZERO;
    }

    my $r = _dynamic_preimage_len($n, _cook_dedekind_psi($n));
    _set_int($r);
}

*psi_inverse_len = \&inverse_dedekind_psi_len;
*inverse_psi_len = \&inverse_dedekind_psi_len;

sub inverse_dedekind_psi_min {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_dedekind_psi($n), min => 1) // return undef;
    bless \$r;
}

*psi_inverse_min = \&inverse_dedekind_psi_min;
*inverse_psi_min = \&inverse_dedekind_psi_min;

sub inverse_dedekind_psi_max {
    my ($n) = @_;

    $n = _any2mpz($$n) // return undef;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_dedekind_psi($n), min => 0) // return undef;
    bless \$r;
}

*psi_inverse_max = \&inverse_dedekind_psi_max;
*inverse_psi_max = \&inverse_dedekind_psi_max;

sub _cook_usigma {
    my ($N) = @_;

    my %L;
    my @D = _divisors($N);

    foreach my $d (@D) {

        Math::Prime::Util::GMP::is_prime_power(Math::Prime::Util::GMP::subint($d, 1)) || next;

        my $u = Math::GMPz::Rmpz_init();
        my $v = Math::GMPz::Rmpz_init();

        (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($u, $d)
          : Math::GMPz::Rmpz_set_str($u, $d, 10);

        Math::GMPz::Rmpz_sub_ui($v, $u, 1);

        push @{$L{$v}}, [$u, $v];
    }

    ([values %L], \@D);
}

sub inverse_usigma {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return _array(ZERO) if !Math::GMPz::Rmpz_sgn($n);
        return _array();
    }

    my $result = _dynamic_preimage($n, _cook_usigma($n), unitary => 1);
    _array([map { bless \$_ } sort { $a <=> $b } @$result]);
}

*usigma_inverse = \&inverse_usigma;

sub _cook_uphi {
    my ($N) = @_;

    my %L;
    my @D = _divisors($N);

    foreach my $d (@D) {

        Math::Prime::Util::GMP::is_prime_power(Math::Prime::Util::GMP::addint($d, 1)) || next;

        my $u = Math::GMPz::Rmpz_init();
        my $v = Math::GMPz::Rmpz_init();

        (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($u, $d)
          : Math::GMPz::Rmpz_set_str($u, $d, 10);

        Math::GMPz::Rmpz_add_ui($v, $u, 1);

        push @{$L{$v}}, [$u, $v];
    }

    ([values %L], \@D);
}

sub inverse_uphi {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return _array(ZERO) if !Math::GMPz::Rmpz_sgn($n);
        return _array();
    }

    my $result = _dynamic_preimage($n, _cook_uphi($n), unitary => 1);
    _array([map { bless \$_ } sort { $a <=> $b } @$result]);
}

*uphi_inverse = \&inverse_uphi;

sub _cook_sigma {
    my ($N, $k) = @_;

    # Based on invphi.gp ver. 2.1 by Max Alekseyev.

    my $q = Math::GMPz::Rmpz_init();
    my $s = Math::GMPz::Rmpz_init();
    my $u = Math::GMPz::Rmpz_init();
    my $v = Math::GMPz::Rmpz_init();

    my %L;
    my @D = _divisors($N);

    foreach my $d (@D) {

        next if ($d == 1);

        (FAST_MODE and $d < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($u, $d)
          : Math::GMPz::Rmpz_set_str($u, $d, 10);

        foreach my $p (map { $_->[0] } _factor_exp(Math::Prime::Util::GMP::subint($d, 1))) {

            (FAST_MODE and $p < ULONG_MAX)
              ? Math::GMPz::Rmpz_set_ui($s, $p)
              : Math::GMPz::Rmpz_set_str($s, $p, 10);

            Math::GMPz::Rmpz_set($q, $s);
            Math::GMPz::Rmpz_pow_ui($q, $q, $k) if ($k > 1);

            # q = d*(p^k - 1) + 1
            Math::GMPz::Rmpz_sub_ui($q, $q, 1);
            Math::GMPz::Rmpz_mul($q, $q, $u);
            Math::GMPz::Rmpz_add_ui($q, $q, 1);

            my $t = Math::GMPz::Rmpz_remove($v, $q, $s);

            next if (($t <= $k) || ($t % $k));

            Math::GMPz::Rmpz_pow_ui($v, $s, $t);
            Math::GMPz::Rmpz_cmp($v, $q) == 0 or next;

            if ($k == 1) {
                Math::GMPz::Rmpz_divexact($v, $v, $s);
            }
            else {
                Math::GMPz::Rmpz_pow_ui($v, $s, Math::Prime::Util::GMP::divint($t, $k) - 1);
            }

            push @{$L{$p}}, [Math::GMPz::Rmpz_init_set($u), Math::GMPz::Rmpz_init_set($v)];
        }
    }

    ([values %L], \@D);
}

sub inverse_sigma {
    my ($n, $k) = @_;

    # Algorithm "invsigma" from invphi.gp ver. 2.1 by Max Alekseyev.

    $n = _any2mpz($$n) // return _array();
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return _array() } : 1;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return _array(ZERO) if !Math::GMPz::Rmpz_sgn($n);
        return _array();
    }

    my $result = _dynamic_preimage($n, _cook_sigma($n, $k));
    _array([map { bless \$_ } sort { $a <=> $b } @$result]);
}

*sigma_inverse = \&inverse_sigma;

sub inverse_sigma_len {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return ZERO;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return ZERO } : 1;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ONE if !Math::GMPz::Rmpz_sgn($n);
        return ZERO;
    }

    my $r = _dynamic_preimage_len($n, _cook_sigma($n, $k));
    _set_int($r);
}

*sigma_inverse_len = \&inverse_sigma_len;

sub inverse_sigma_min {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return undef;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return undef } : 1;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_sigma($n, $k), min => 1) // return undef;
    bless \$r;
}

*sigma_inverse_min = \&inverse_sigma_min;

sub inverse_sigma_max {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return undef;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // return undef } : 1;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO if !Math::GMPz::Rmpz_sgn($n);
        return undef;
    }

    my $r = _dynamic_preimage_minmax($n, _cook_sigma($n, $k), min => 0) // return undef;
    bless \$r;
}

*sigma_inverse_max = \&inverse_sigma_max;

sub jordan_totient {
    my ($n, $k) = @_;

    if (!defined($k)) {
        return $n->euler_phi;
    }

    # Multiplicative with:
    #   a(p^e, k) = p^(k*e) - p^(k*e - k)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 1) {
        return $n->euler_phi;
    }

    $n = $$n;
    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    if (CORE::length($n) < SPECIAL_FACTORS_MIN) {
        return _set_int(Math::Prime::Util::GMP::jordan_totient($k, $n));
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if ($e == 1) {

            if (FAST_MODE and $p < ULONG_MAX) {

                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
                Math::GMPz::Rmpz_sub_ui($t, $t, 1);

                if (Math::GMPz::Rmpz_fits_ulong_p($t)) {
                    push @terms, Math::GMPz::Rmpz_get_ui($t);
                    next;
                }
            }
            else {
                Math::GMPz::Rmpz_set_str($t, "$p", 10);
                Math::GMPz::Rmpz_pow_ui($t, $t, $k) if ($k > 1);
                Math::GMPz::Rmpz_sub_ui($t, $t, 1);
            }

            push @terms, Math::GMPz::Rmpz_init_set($t);
            next;
        }

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
            Math::GMPz::Rmpz_pow_ui($u, $t, $e - 1);
            Math::GMPz::Rmpz_mul($t, $t, $u);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_set($u, $t);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k);
            Math::GMPz::Rmpz_pow_ui($u, $t, $e - 1);
            Math::GMPz::Rmpz_mul($t, $t, $u);
        }

        Math::GMPz::Rmpz_sub($t, $t, $u);
        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

*JordanTotient = \&jordan_totient;

sub dedekind_sum {
    my ($h, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    $h = _any2mpz($$h) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    Math::GMPz::Rmpz_sgn($k) > 0 or goto &nan;

    my $sum = Math::GMPq::Rmpq_init();
    Math::GMPq::Rmpq_set_ui($sum, 0, 1);

    state $H = Math::GMPz::Rmpz_init_nobless();
    state $K = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set($K, $k);
    Math::GMPz::Rmpz_mod($H, $h, $K);

    my $sign = 1;
    state $t1   = Math::GMPz::Rmpz_init_nobless();
    state $t2   = Math::GMPz::Rmpz_init_nobless();
    state $term = Math::GMPq::Rmpq_init_nobless();

    while (Math::GMPz::Rmpz_sgn($H) > 0) {

        # num = H^2 + K^2 + 1 - 3HK
        Math::GMPz::Rmpz_mul($t1, $H, $H);
        Math::GMPz::Rmpz_mul($t2, $K, $K);
        Math::GMPz::Rmpz_add($t1, $t1, $t2);
        Math::GMPz::Rmpz_add_ui($t1, $t1, 1);

        Math::GMPz::Rmpz_mul($t2, $H, $K);
        Math::GMPz::Rmpz_mul_ui($t2, $t2, 3);
        Math::GMPz::Rmpz_sub($t1, $t1, $t2);

        # den = 12HK
        Math::GMPz::Rmpz_mul($t2, $H, $K);
        Math::GMPz::Rmpz_mul_ui($t2, $t2, 12);

        Math::GMPq::Rmpq_set_num($term, $t1);
        Math::GMPq::Rmpq_set_den($term, $t2);
        Math::GMPq::Rmpq_canonicalize($term);

        if ($sign == 1) {
            Math::GMPq::Rmpq_add($sum, $sum, $term);
        }
        else {
            Math::GMPq::Rmpq_sub($sum, $sum, $term);
        }
        $sign = -$sign;

        Math::GMPz::Rmpz_set($t1, $H);
        Math::GMPz::Rmpz_mod($H, $K, $H);
        Math::GMPz::Rmpz_set($K, $t1);
    }
    return bless \$sum;
}

sub dedekind_psi {
    my ($n, $k) = @_;

    # Multiplicative with:
    #   a(p^e, k) = p^(k*e) + p^(k*e - k)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    return $n->usigma0 if ($k == 0);

    $n = $$n;
    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if ($e == 1) {

            if (FAST_MODE and $p < ULONG_MAX) {

                if ($k == 1) {
                    push @terms, $p + 1;
                    next;
                }

                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
                Math::GMPz::Rmpz_add_ui($t, $t, 1);

                if (Math::GMPz::Rmpz_fits_ulong_p($t)) {
                    push @terms, Math::GMPz::Rmpz_get_ui($t);
                    next;
                }
            }
            else {
                Math::GMPz::Rmpz_set_str($t, "$p", 10);
                Math::GMPz::Rmpz_pow_ui($t, $t, $k) if ($k > 1);
                Math::GMPz::Rmpz_add_ui($t, $t, 1);
            }

            push @terms, Math::GMPz::Rmpz_init_set($t);
            next;
        }

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
            Math::GMPz::Rmpz_pow_ui($u, $t, $e - 1);
            Math::GMPz::Rmpz_mul($t, $t, $u);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_set($u, $t);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k);
            Math::GMPz::Rmpz_pow_ui($u, $t, $e - 1);
            Math::GMPz::Rmpz_mul($t, $t, $u);
        }

        Math::GMPz::Rmpz_add($t, $t, $u);
        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

*psi         = \&dedekind_psi;
*DedekindPsi = \&dedekind_psi;

sub dedekind_psi_sum {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    my $k_obj = bless \$k;

    my $f = sub { $_[0]->is_squarefree ? 1 : 0 };
    my $g = sub { $_[0]->ipow($k_obj) };

    my $F = sub { $_[0]->squarefree_count };
    my $G = sub { _set_int(Math::Prime::Util::GMP::powersum(${$_[0]}, $k)) };

    if ($k == 0) {
        $g = sub { 1 };
        $G = sub { $_[0] };
    }
    elsif ($k == 1) {
        $g = sub { $_[0] };
    }
    elsif ($k == 2) {
        $g = sub { $_[0]->sqr };
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

*psi_sum = \&dedekind_psi_sum;

sub carmichael_lambda {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n) and $n >= 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::carmichael_lambda($n)
          : Math::Prime::Util::GMP::carmichael_lambda($n);
        return bless \$r;
    }

    $n = _big2uistr($n) // goto &nan;

    if (CORE::length($n) >= SPECIAL_FACTORS_MIN) {
        my @lambdas;
        foreach my $pp (_factor_exp($n)) {
            my ($p, $e) = @$pp;
            if ($e == 1) {
                push @lambdas, ((FAST_MODE and $p < ULONG_MAX) ? ($p - 1) : Math::Prime::Util::GMP::subint($p, 1));
            }
            else {
                push @lambdas, Math::Prime::Util::GMP::carmichael_lambda(Math::Prime::Util::GMP::powint($p, $e));
            }
        }
        return _set_int(Math::Prime::Util::GMP::lcm(@lambdas));
    }

    _set_int(Math::Prime::Util::GMP::carmichael_lambda($n));
}

*lambda          = \&carmichael_lambda;
*reduced_totient = \&carmichael_lambda;

sub liouville {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n) and $n > 0) {
        my $r =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::liouville($n)
          : Math::Prime::Util::GMP::liouville($n);
        return bless \$r;
    }

    my $str = _big2uistr($n) // goto &nan;

    if ($str eq '0') {
        return ZERO;
    }

    Math::Prime::Util::GMP::liouville($str) eq '1' ? ONE : MONE;
}

*Liouville        = \&liouville;
*liouville_lambda = \&liouville;

sub bigomega {
    my ($n, $m) = @_;

    $m = defined($m) ? do { ref($m) eq __PACKAGE__ or _valid(\$m); _any2ui($$m) // goto &nan } : 0;

    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }

    $n eq '0' and return ZERO;

    if ($m == 0) {

        my $r;
        if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
            $r = Math::Prime::Util::prime_bigomega($n);
        }
        else {
            my @factors = _factor($n);
            $r = scalar @factors;
        }

        return bless \$r;
    }

    # Ω_m(n) = Sum_{p^k|n} Sum_{j=1..k} n^m / p^(j*m)
    #        = Sum_{p^k|n} n^m * (p^(m*k) - 1) / (p^m - 1) / p^(m*k)

    my $t  = Math::GMPz::Rmpz_init();
    my $u  = Math::GMPz::Rmpz_init();
    my $nm = Math::GMPz::Rmpz_init_set_str($n, 10);

    Math::GMPz::Rmpz_pow_ui($nm, $nm, $m) if $m > 1;

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pp (_factor_exp($n)) {
        my ($p, $k) = @$pp;

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($u, $p, $m);    # u = p^m
        }
        else {
            Math::GMPz::Rmpz_set_str($u, $p, 10);
            Math::GMPz::Rmpz_pow_ui($u, $u, $m);       # u = p^m
        }

        Math::GMPz::Rmpz_pow_ui($t, $u, $k);      # t = (p^m)^k = p^(m*k)
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);       # u = p^m - 1
        Math::GMPz::Rmpz_mul($u, $u, $t);         # u = (p^m - 1) * p^(m*k)
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);       # t = p^(m*k) - 1
        Math::GMPz::Rmpz_mul($t, $t, $nm);        # t = n^m * (p^(m*k) - 1)
        Math::GMPz::Rmpz_divexact($t, $t, $u);    # t = (n^m * (p^(m*k) - 1)) / ((p^m - 1) * p^(m*k))

        Math::GMPz::Rmpz_add($sum, $sum, $t);
    }

    bless \$sum;
}

*Ω     = \&bigomega;
*Omega = \&bigomega;

sub bigomega_sum {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 0;

    my $k_obj = bless \$k;

    my $f = sub { $_[0]->ipow($k_obj) };
    my $g = sub { (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime_power(${$_[0]}) : Math::Prime::Util::GMP::is_prime_power(${$_[0]})) ? 1 : 0 };

    my $F = sub { $_[0]->faulhaber_sum($k_obj) };
    my $G = sub { HAS_PRIME_UTIL ? Math::Prime::Util::prime_power_count(${$_[0]}) : $_[0]->prime_power_count };

    if ($k == 1) {
        $f = sub { $_[0] };
    }
    elsif ($k == 2) {
        $f = sub { $_[0]->sqr };
    }

    if ($k == 0) {
        $f = sub { 1 };
        $F = sub { $_[0] };
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

sub prime_power_sigma0 {
    $_[0]->bigomega;
}

sub collatz {
    my ($n) = @_;

    $n = _any2mpz($$n) // goto &nan;
    $n = Math::GMPz::Rmpz_init_set($n);    # copy

    state $two = Math::GMPz::Rmpz_init_set_ui(2);
    my $count = Math::GMPz::Rmpz_even_p($n) ? Math::GMPz::Rmpz_remove($n, $n, $two) : 0;

    while (Math::GMPz::Rmpz_cmp_ui($n, 1) > 0) {

        Math::GMPz::Rmpz_mul_ui($n, $n, 3);
        Math::GMPz::Rmpz_add_ui($n, $n, 1);

        $count += 1 + Math::GMPz::Rmpz_remove($n, $n, $two);
    }

    _set_int($count);
}

sub omega {
    my ($n, $m) = @_;

    $m = defined($m) ? do { ref($m) eq __PACKAGE__ or _valid(\$m); _any2ui($$m) // goto &nan } : 0;
    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }

    $n eq '0' and return ZERO;

    if ($m == 0) {

        my $r;
        if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
            $r = Math::Prime::Util::prime_omega($n);
        }
        else {
            $r = scalar _factor_exp($n);
        }

        return bless \$r;
    }

    # omega_m(n) = n^m * Sum_{p|n} 1/p^m

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $nm = Math::GMPz::Rmpz_init_set_str($n, 10);

    Math::GMPz::Rmpz_pow_ui($nm, $nm, $m) if ($m > 1);

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pe (_factor_exp($n)) {

        my $p = $pe->[0];

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $m);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $m);
        }

        Math::GMPz::Rmpz_divexact($t, $nm, $t);
        Math::GMPz::Rmpz_add($sum, $sum, $t);
    }

    bless \$sum;
}

*ω = \&omega;

sub omega_sum {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 0;

    my $k_obj = bless \$k;

    my $f = sub { $_[0]->ipow($k_obj) };
    my $g = sub { (HAS_PRIME_UTIL ? Math::Prime::Util::is_prime(${$_[0]}) : Math::Prime::Util::GMP::is_prime(${$_[0]})) ? 1 : 0 };

    my $F = sub { $_[0]->faulhaber_sum($k_obj) };
    my $G = sub { HAS_PRIME_UTIL ? Math::Prime::Util::prime_count(${$_[0]}) : $_[0]->prime_count };

    if ($k == 1) {
        $f = sub { $_[0] };
    }
    elsif ($k == 2) {
        $f = sub { $_[0]->sqr };
    }

    if ($k == 0) {
        $f = sub { 1 };
        $F = sub { $_[0] };
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

sub prime_sigma0 {
    $_[0]->omega;
}

sub prime_power_usigma0 {
    $_[0]->omega;
}

sub usigma0 {
    my ($n) = @_;

    # Identity:
    #   usigma0(n) = 2^omega(n)

    $n = $$n;
    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
        return _set_int(1 << (Math::Prime::Util::prime_omega($n)));
    }

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($r, 0);
    Math::GMPz::Rmpz_setbit($r, scalar _factor_exp($n));
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub usigma {
    my ($n, $k) = @_;

    # Interesting identity:
    #   usigma(n, k) = sigma(n^(2*k) / rad(n)) / sigma(n^k / rad(n))

    # Multiplicative with:
    #   usigma(p^e, k) = p^(k*e) + 1

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &usigma0;
    }

    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {

            if ($e == 1 and $k == 1) {    # optimization
                push @terms, $p + 1;
                next;
            }

            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k * $e);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k * $e);
        }

        Math::GMPz::Rmpz_add_ui($t, $t, 1);

        push @terms, (Math::GMPz::Rmpz_fits_ulong_p($t) ? Math::GMPz::Rmpz_get_ui($t) : Math::GMPz::Rmpz_init_set($t));
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub usigma_sum {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;

    # Fast sublinear path for k=0: sum_{i=1..n} 2^omega(i)
    if (HAS_PRIME_UTIL and $k == 0) {
        return _set_int($n)->exp_omega_sum(TWO);
    }

    my $s  = Math::Prime::Util::GMP::sqrtint($n);
    my $ss = Math::Prime::Util::GMP::sqrtint($s);

    my @M = (0);
    my @F = (0);

    my @moebius =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::moebius(0, $s)
      : Math::Prime::Util::GMP::moebius(0, $s);

    # Precompute F(i) = mu(i) * i^k and its prefix sums M(i)
    for my $i (1 .. $s) {
        my $mu = $moebius[$i];

        if ($mu == 0) {
            push @F, 0;
            push @M, $M[-1];
            next;
        }

        my $ik = ($k == 0) ? 1 : ($k == 1) ? $i : ($k == 2) ? Math::Prime::Util::GMP::mulint($i, $i) : Math::Prime::Util::GMP::powint($i, $k);
        my $t  = $mu == -1 ? "-$ik" : $ik;

        push @F, $t;
        push @M, Math::Prime::Util::GMP::addint($M[-1], $t);
    }

    # Compute Part A: Sum_{i=1..ss} F[i] * sigma_sum(floor(n / i^2), k)
    my @terms;
    for my $i (1 .. $ss) {
        $F[$i] || next;

        my $i2   = Math::Prime::Util::GMP::mulint($i, $i);
        my $nod2 = Math::Prime::Util::GMP::divint($n, $i2);

        push @terms, Math::Prime::Util::GMP::mulint($F[$i], _sigma_sum($nod2, $k));
    }

    # Compute Part B: Sum_{i=1..s} sigma_k(i) * M[floor(sqrt(floor(n / i)))]
    for my $i (1 .. $s) {
        my $div_sum =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divisor_sum($i, $k)
          : Math::Prime::Util::GMP::sigma($i, $k);

        my $m_idx = Math::Prime::Util::GMP::sqrtint(Math::Prime::Util::GMP::divint($n, $i));
        push @terms, Math::Prime::Util::GMP::mulint($div_sum, $M[$m_idx]);
    }

    my $total = Math::Prime::Util::GMP::vecsum(@terms);

    # Final result: A + B - sigma_sum(s, k) * M[ss]
    $total = Math::Prime::Util::GMP::subint($total, Math::Prime::Util::GMP::mulint(_sigma_sum($s, $k), $M[$ss]));

    _set_int($total);
}

sub usigma0_sum {
    $_[0]->usigma_sum(ZERO);
}

sub nisigma0 {    # A348341: count non-infinitary divisors of n
    my ($n) = @_;
    $n->sigma0->sub($n->isigma0);
}

sub nisigma {    # A348271: sum of non-infinitary divisors of n
    my ($n, $k) = @_;
    $n->sigma($k)->sub($n->isigma($k));
}

sub nisigma_sum {
    my ($n, $k) = @_;
    $n->sigma_sum($k)->sub($n->isigma_sum($k));
}

sub nisigma0_sum {
    my ($n) = @_;
    $n->sigma0_sum->sub($n->isigma0_sum);
}

sub nusigma0 {    # A048105: count of non-unitary divisors of n
    my ($n) = @_;
    $n->sigma0->sub($n->usigma0);
}

sub nusigma {    # A048146: sum of non-unitary divisors of n
    my ($n, $k) = @_;
    $n->sigma($k)->sub($n->usigma($k));
}

sub nusigma_sum {
    my ($n, $k) = @_;
    $n->sigma_sum($k)->sub($n->usigma_sum($k));
}

sub nusigma0_sum {
    my ($n) = @_;
    $n->sigma0_sum->sub($n->usigma0_sum);
}

sub bsigma0 {    # A286324: count of bi-unitary divisors of n.
    my ($n) = @_;

    # Multiplicative with:
    #   a(p^e) = e + (e mod 2)

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;
    _set_int(Math::Prime::Util::GMP::vecprod(map { $_->[1] + ($_->[1] % 2) } _factor_exp($n)));
}

*biusigma0 = \&bsigma0;

sub bsigma {    # A188999: Bi-unitary sigma: sum of the bi-unitary divisors of n
    my ($n, $k) = @_;

    # Multiplicative with:
    #   bsigma(p^e, k) = (p^(k*(e+1)) - 1)/(p^k - 1)                   if e is odd
    #   bsigma(p^e, k) = (p^(k*(e+1)) - 1)/(p^k - 1) - p^(k*(e/2))     if e is even

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &bsigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($u, $p, $k);
            Math::GMPz::Rmpz_pow_ui($t, $u, $e + 1);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($u, $t, $k);
            Math::GMPz::Rmpz_pow_ui($t, $u, $e + 1);
        }

        Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);
        Math::GMPz::Rmpz_divexact($t, $t, $u);

        if ($e % 2 == 0) {
            Math::GMPz::Rmpz_add_ui($u, $u, 1);
            Math::GMPz::Rmpz_pow_ui($u, $u, $e >> 1);
            Math::GMPz::Rmpz_sub($t, $t, $u);
        }

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

*biusigma = \&bsigma;

sub _bsigma_squarefull_aux {
    my ($n, $k) = @_;

    if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
        return Math::Prime::Util::vecprod(
            map {
                my ($p, $e) = @$_;
                $e == 2           ? ($k == 1 ? (-$p) : join('', '-', Math::Prime::Util::GMP::powint($p, $k)))
                  : ($e % 2 == 0) ? Math::Prime::Util::mulint(-2, Math::Prime::Util::powint($p, $k * ($e >> 1)))
                  :   Math::Prime::Util::addint(Math::Prime::Util::powint($p, $k * (($e >> 1) + 1)), Math::Prime::Util::powint($p, ($e >> 1) * $k));
            } grep { $_->[1] >= 2 } Math::Prime::Util::factor_exp($n)
        );
    }

    Math::Prime::Util::GMP::vecprod(
        map {
            my ($p, $e) = @$_;
            $e == 2           ? (($k == 1) ? "-$p" : ('-' . Math::Prime::Util::GMP::powint($p, $k)))
              : ($e % 2 == 0) ? Math::Prime::Util::GMP::mulint(-2, Math::Prime::Util::GMP::powint($p, $k * ($e >> 1)))
              :   Math::Prime::Util::GMP::addint(Math::Prime::Util::GMP::powint($p, $k * (($e >> 1) + 1)), Math::Prime::Util::GMP::powint($p, ($e >> 1) * $k));
        } grep { $_->[1] >= 2 } _factor_exp($n)
    );
}

sub bsigma_sum {
    my ($n, $k) = @_;

    $n = _big2uistr($$n) // return ZERO;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    my $s = Math::Prime::Util::GMP::sqrtint($n);
    my $P = HAS_PRIME_UTIL ? Math::Prime::Util::powerful_numbers(1, $n) : [map { $$_ } @{_set_int($n)->squarefull}];

    my @F = ();
    my @S = (0);

    foreach my $m (@$P) {
        my $t = _bsigma_squarefull_aux($m, $k);
        push(@F, $t) if !($m > $s);
        push @S, HAS_PRIME_UTIL
          ? Math::Prime::Util::addint($S[-1], $t)
          : Math::Prime::Util::GMP::addint($S[-1], $t);
    }

    my @terms;
    foreach my $i (0 .. $#F) {
        my $n_div_p = Math::Prime::Util::GMP::divint($n, $P->[$i]);
        push @terms, Math::Prime::Util::GMP::mulint($F[$i], _sigma_sum($n_div_p, $k));
    }

    foreach my $m (1 .. $s) {
        my $t =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n, $m)
          : Math::Prime::Util::GMP::divint($n, $m);
        my $c =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powerful_count($t)
          : Math::Prime::Util::GMP::powerful_count($t);
        my $sig_k_m =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divisor_sum($m, $k)
          : Math::Prime::Util::GMP::sigma($m, $k);
        push @terms, HAS_PRIME_UTIL
          ? Math::Prime::Util::mulint($sig_k_m, $S[$c])
          : Math::Prime::Util::GMP::mulint($sig_k_m, $S[$c]);
    }

    my $A = Math::Prime::Util::GMP::vecsum(@terms);
    my $B = Math::Prime::Util::GMP::mulint(_sigma_sum($s, $k), $S[Math::Prime::Util::GMP::powerful_count($s)]);

    _set_int(Math::Prime::Util::GMP::subint($A, $B));
}

sub bsigma0_sum {
    $_[0]->bsigma_sum(ZERO);
}

sub nbsigma0 {    # A390957: count of non-bi-unitary divisors of n
    my ($n) = @_;
    $n->sigma0->sub($n->bsigma0);
}

sub nbsigma {    # A319072: sum of non-bi-unitary divisors of n.
    my ($n, $k) = @_;
    $n->sigma($k)->sub($n->bsigma($k));
}

sub nbsigma_sum {
    my ($n, $k) = @_;
    $n->sigma_sum($k)->sub($n->bsigma_sum($k));
}

sub nbsigma0_sum {
    my ($n) = @_;
    $n->sigma0_sum->sub($n->bsigma0_sum);
}

sub isigma0 {    # A037445: count of infinitary divisors (or i-divisors) of n
    my ($n) = @_;

    # Multiplicative with:
    #   a(p^e) = 2^hammingweight(e)

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;
    _set_int(
             Math::Prime::Util::GMP::vecprod(
                      map { 1 << (HAS_PRIME_UTIL ? Math::Prime::Util::hammingweight($_->[1]) : Math::Prime::Util::GMP::hammingweight($_->[1])) } _factor_exp($n)
             )
            );
}

sub isigma {    # A049417: sum of infinitary divisors of n
    my ($n, $k) = @_;

    # Multiplicative with:
    #   If e = Sum_{k >= 0} d_k 2^k (binary representation of e), then
    #   isigma(p^e, r) = Product_{k >= 0} (p^(r*2^k*{d_k+1}) - 1)/(p^(r*2^k) - 1)

    # Simplified formula, where d_k is odd in the binary representation of e (ignore even d_k):
    #   isigma(p^e, r) = Product_{k >= 0} (p^(r * 2^k) + 1)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &isigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($t, $p)
          : Math::GMPz::Rmpz_set_str($t, "$p", 10);

        my $j = 0;
        do {
            if ($e % 2 == 1) {
                Math::GMPz::Rmpz_pow_ui($u, $t, (1 << $j) * $k);
                Math::GMPz::Rmpz_add_ui($u, $u, 1);
                push @terms,
                  (
                      Math::GMPz::Rmpz_fits_ulong_p($u)
                    ? Math::GMPz::Rmpz_get_ui($u)
                    : Math::GMPz::Rmpz_init_set($u)
                  );
            }
            ++$j;
        } while ($e >>= 1);
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub _isigma_squarefull_aux {
    my ($n, $k) = @_;

    state %cache;

    if (scalar(%cache) > 1e6) {
        undef %cache;
    }

    Math::Prime::Util::GMP::vecprod(
        map {
            my ($p, $e) = @$_;

            $cache{$p}{$e}{$k} //= do {
                my $pk        = ($k == 1) ? $p : Math::Prime::Util::GMP::powint($p, $k);
                my $pk_plus_1 = Math::Prime::Util::GMP::addint($pk, 1);

                my @pow_vals = ($pk);
                my @isig_res;

                for my $exp ($e, $e - 1, $e - 2) {
                    if ($exp == 0) {
                        push @isig_res, 1;
                        next;
                    }
                    my $ans  = 1;
                    my $i    = 0;
                    my $temp = $exp;
                    while ($temp > 0) {
                        if ($temp & 1) {
                            while (@pow_vals <= $i) {
                                push @pow_vals, Math::Prime::Util::GMP::mulint($pow_vals[-1], $pow_vals[-1]);
                            }
                            $ans = Math::Prime::Util::GMP::mulint($ans, Math::Prime::Util::GMP::addint($pow_vals[$i], 1));
                        }
                        $temp >>= 1;
                        $i++;
                    }
                    push @isig_res, $ans;
                }

                my ($t1, $t2_raw, $t3_raw) = @isig_res;
                my $t2 = Math::Prime::Util::GMP::mulint($pk_plus_1, $t2_raw);
                my $t3 = Math::Prime::Util::GMP::mulint($pk,        $t3_raw);

                Math::Prime::Util::GMP::addint(Math::Prime::Util::GMP::subint($t1, $t2), $t3);
            };
        } grep { $_->[1] >= 2 } (HAS_PRIME_UTIL ? Math::Prime::Util::factor_exp($n) : _factor_exp($n))
    );
}

sub isigma_sum {
    my ($n, $k) = @_;

    $n = _big2uistr($$n) // return ZERO;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    my $s = Math::Prime::Util::GMP::sqrtint($n);
    my $P = HAS_PRIME_UTIL ? Math::Prime::Util::powerful_numbers(1, $n) : [map { $$_ } @{_set_int($n)->squarefull}];

    my @F = ();
    my @S = (0);

    foreach my $m (@$P) {
        my $t = _isigma_squarefull_aux($m, $k);
        push(@F, $t) if !($m > $s);
        push @S, HAS_PRIME_UTIL
          ? Math::Prime::Util::addint($S[-1], $t)
          : Math::Prime::Util::GMP::addint($S[-1], $t);
    }

    my @terms;
    foreach my $i (0 .. $#F) {
        my $n_div_p = Math::Prime::Util::GMP::divint($n, $P->[$i]);
        push @terms, Math::Prime::Util::GMP::mulint($F[$i], _sigma_sum($n_div_p, $k));
    }

    foreach my $m (1 .. $s) {
        my $t =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n, $m)
          : Math::Prime::Util::GMP::divint($n, $m);
        my $c =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powerful_count($t)
          : Math::Prime::Util::GMP::powerful_count($t);
        my $sig_k_m =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divisor_sum($m, $k)
          : Math::Prime::Util::GMP::sigma($m, $k);
        push @terms, HAS_PRIME_UTIL
          ? Math::Prime::Util::mulint($sig_k_m, $S[$c])
          : Math::Prime::Util::GMP::mulint($sig_k_m, $S[$c]);
    }

    my $A = Math::Prime::Util::GMP::vecsum(@terms);
    my $B = Math::Prime::Util::GMP::mulint(_sigma_sum($s, $k), $S[Math::Prime::Util::GMP::powerful_count($s)]);

    _set_int(Math::Prime::Util::GMP::subint($A, $B));
}

sub isigma0_sum {
    $_[0]->isigma_sum(ZERO);
}

sub esigma0 {    # A049419: count of exponential divisors (or e-divisors) of n.
    my ($n) = @_;

    # Multiplicative with:
    #   a(p^e) = tau(e)

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    my @terms;
    foreach my $pp (_factor_exp($n)) {
        if ($pp->[1] > 1) {
            push @terms,
              (
                HAS_PRIME_UTIL
                ? Math::Prime::Util::divisor_sum($pp->[1], 0)
                : Math::Prime::Util::GMP::sigma($pp->[1], 0)
              );
        }
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub esigma {    # A051377: sum of exponential divisors (or e-divisors) of n
    my ($n, $k) = @_;

    # Multiplicative with:
    #   a(p^e) = Sum_{d|e} p^d

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &esigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $w = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        Math::GMPz::Rmpz_set_ui($t, 1);

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($t, $p)
          : Math::GMPz::Rmpz_set_str($t, $p, 10);

        my @e_divisors =
          ($e > 1)
          ? (
             HAS_PRIME_UTIL
             ? Math::Prime::Util::divisors($e)
             : _divisors($e)
            )
          : (1);

        Math::GMPz::Rmpz_set_ui($u, 0);

        foreach my $d (@e_divisors) {
            Math::GMPz::Rmpz_pow_ui($w, $t, $k * $d);
            Math::GMPz::Rmpz_add($u, $u, $w);
        }

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($u)
            ? Math::GMPz::Rmpz_get_ui($u)
            : Math::GMPz::Rmpz_init_set($u)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub nesigma0 {    # A160097: count of non-exponential divisors of n
    my ($n) = @_;
    $n->sigma0->sub($n->esigma0);
}

sub nesigma {    # A160135: sum of non-exponential divisors of n
    my ($n, $k) = @_;
    $n->sigma($k)->sub($n->esigma($k));
}

sub uphi {    # OEIS: A047994
    my ($n, $k) = @_;

    # Multiplicative with:
    #   uphi(p^e, k) = p^(e*k) - 1

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {

            if ($e == 1 and $k == 1) {    # optimization
                push @terms, $p - 1;
                next;
            }

            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e * $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $e * $k);
        }

        Math::GMPz::Rmpz_sub_ui($t, $t, 1);

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub _uphi_squarefull_aux {    # OEIS: A396336
    my ($n, $k) = @_;

    if (HAS_PRIME_UTIL and $n < ULONG_MAX) {
        return Math::Prime::Util::vecprod(
            map {
                my ($p, $e) = @$_;
                Math::Prime::Util::mulint(
                                          ($e - 1),
                                          (
                                             ($k == 1)
                                           ? ($p - 1)
                                           : Math::Prime::Util::subint(Math::Prime::Util::powint($p, $k), 1)
                                          )
                                         )
            } grep { $_->[1] >= 2 } Math::Prime::Util::factor_exp($n)
        );
    }

    Math::Prime::Util::GMP::vecprod(
        map {
            my ($p, $e) = @$_;
            Math::Prime::Util::GMP::mulint(($e - 1),
                                           ($k == 1)
                                           ? Math::Prime::Util::GMP::subint($p,                                     1)
                                           : Math::Prime::Util::GMP::subint(Math::Prime::Util::GMP::powint($p, $k), 1))
        } grep { $_->[1] >= 2 } _factor_exp($n)
    );
}

sub uphi_sum {
    my ($n, $j) = @_;

    $n = _big2uistr($$n) // return ZERO;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    my $s = Math::Prime::Util::GMP::sqrtint($n);
    my $P = HAS_PRIME_UTIL ? Math::Prime::Util::powerful_numbers(1, $n) : [map { $$_ } @{_set_int($n)->squarefull}];

    my @F = ();
    my @S = (0);

    foreach my $k (@$P) {
        my $t = _uphi_squarefull_aux($k, $j);
        push(@F, $t) if !($k > $s);
        push @S, HAS_PRIME_UTIL
          ? Math::Prime::Util::addint($S[-1], $t)
          : Math::Prime::Util::GMP::addint($S[-1], $t);
    }

    my $j_obj = bless \$j;

    my @terms;
    foreach my $i (0 .. $#F) {
        push @terms, Math::Prime::Util::GMP::mulint($F[$i], ${_set_int(Math::Prime::Util::GMP::divint($n, $P->[$i]))->phi_sum($j_obj)});
    }

    foreach my $k (1 .. $s) {
        my $t =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n, $k)
          : Math::Prime::Util::GMP::divint($n, $k);
        my $c =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powerful_count($t)
          : Math::Prime::Util::GMP::powerful_count($t);
        my $Jk =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::jordan_totient($j, $k)
          : Math::Prime::Util::GMP::jordan_totient($j, $k);
        push @terms, HAS_PRIME_UTIL
          ? Math::Prime::Util::mulint($Jk, $S[$c])
          : Math::Prime::Util::GMP::mulint($Jk, $S[$c]);
    }

    my $A = Math::Prime::Util::GMP::vecsum(@terms);
    my $B = Math::Prime::Util::GMP::mulint(${_set_int($s)->phi_sum($j_obj)}, $S[Math::Prime::Util::GMP::powerful_count($s)]);

    _set_int(Math::Prime::Util::GMP::subint($A, $B));
}

sub nuphi {    # OEIS: A254503 (Generalized for k)
    my ($n, $k) = @_;

    # Multiplicative with:
    #   a(p, k) = p^k
    #   a(p^e, k) = J_k(p^e) = (p^k - 1) * p^(k*(e-1)), for e > 1.

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {

            if ($e == 1 and $k == 1) {    # Optimization for e=1, k=1
                push @terms, $p;
                next;
            }

            if ($e == 1) {
                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
            }
            else {
                Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k * ($e - 1));
                if ($k == 1) {
                    Math::GMPz::Rmpz_mul_ui($t, $t, $p - 1);
                }
                else {
                    Math::GMPz::Rmpz_ui_pow_ui($u, $p, $k);
                    Math::GMPz::Rmpz_sub_ui($u, $u, 1);
                    Math::GMPz::Rmpz_mul($t, $t, $u);
                }
            }
        }
        elsif ($e == 1) {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($u, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $u, $k * ($e - 1));

            if ($k == 1) {
                Math::GMPz::Rmpz_sub_ui($u, $u, 1);
            }
            else {
                Math::GMPz::Rmpz_pow_ui($u, $u, $k);
                Math::GMPz::Rmpz_sub_ui($u, $u, 1);
            }
            Math::GMPz::Rmpz_mul($t, $t, $u);
        }

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub nuphi_sum {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;

    my $s  = Math::Prime::Util::GMP::sqrtint($n);
    my $ss = Math::Prime::Util::GMP::sqrtint($s);

    my @M = (0);
    my @F = (0);

    my @moebius =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::moebius(0, $s)
      : Math::Prime::Util::GMP::moebius(0, $s);

    # Precompute F(i) = mu(i) * i^k and its prefix sums M(i)
    for my $i (1 .. $s) {
        my $mu = $moebius[$i];

        if ($mu == 0) {
            push @F, 0;
            push @M, $M[-1];
            next;
        }

        my $ik = ($k == 0) ? 1 : ($k == 1) ? $i : ($k == 2) ? Math::Prime::Util::GMP::mulint($i, $i) : Math::Prime::Util::GMP::powint($i, $k);
        my $t  = $mu == -1 ? "-$ik" : $ik;

        push @F, $t;
        push @M, Math::Prime::Util::GMP::addint($M[-1], $t);
    }

    my @terms;
    for my $i (1 .. $ss) {
        $F[$i] || next;

        my $i2   = Math::Prime::Util::GMP::mulint($i, $i);
        my $nod2 = Math::Prime::Util::GMP::divint($n, $i2);

        push @terms, Math::Prime::Util::GMP::mulint($F[$i], Math::Prime::Util::GMP::powersum($nod2, $k));
    }

    for my $i (1 .. $s) {
        my $div_sum =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powint($i, $k)
          : Math::Prime::Util::GMP::powint($i, $k);

        my $m_idx = Math::Prime::Util::GMP::sqrtint(Math::Prime::Util::GMP::divint($n, $i));
        push @terms, Math::Prime::Util::GMP::mulint($div_sum, $M[$m_idx]);
    }

    my $total = Math::Prime::Util::GMP::vecsum(@terms);
    $total = Math::Prime::Util::GMP::subint($total, Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::powersum($s, $k), $M[$ss]));

    _set_int($total);
}

sub iphi {    # OEIS: A091732 -- infinitary analog of Euler's phi function
    my ($n, $k) = @_;

    # Let e = Sum_{k >= 0} d_k 2^k (binary representation of e).

    # Multiplicative with, where d_k is odd in the binary representation of e (ignore even d_k):
    #   iphi(p^e, r) = Product_{k >= 0} (p^(r * 2^k) - 1)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = $$n;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        (FAST_MODE and $p < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($t, $p)
          : Math::GMPz::Rmpz_set_str($t, "$p", 10);

        my $j = 0;
        do {
            if ($e % 2 == 1) {
                Math::GMPz::Rmpz_pow_ui($u, $t, (1 << $j) * $k);
                Math::GMPz::Rmpz_sub_ui($u, $u, 1);
                push @terms,
                  (
                      Math::GMPz::Rmpz_fits_ulong_p($u)
                    ? Math::GMPz::Rmpz_get_ui($u)
                    : Math::GMPz::Rmpz_init_set($u)
                  );
            }
            ++$j;
        } while ($e >>= 1);
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub _iphi_squarefull_aux {
    my ($n, $k) = @_;

    state %cache;

    if (scalar(%cache) > 1e6) {
        undef %cache;
    }

    Math::Prime::Util::GMP::vecprod(
        map {
            my ($p, $e) = @$_;

            $cache{$p}{$e}{$k} //= do {
                my $pk = Math::Prime::Util::GMP::powint($p, $k);

                my @P = ($pk);
                my @T = (Math::Prime::Util::GMP::subint($pk, "1"));
                my @h = ("1", "0");
                my @J = ("0", $T[0]);

                for my $i (2 .. $e) {
                    my $iphi = "1";
                    for (my ($temp_i, $bit) = ($i, 0) ; $temp_i > 0 ; ($temp_i >>= 1, $bit++)) {
                        if ($temp_i & 1) {
                            while (@P <= $bit) {
                                push @P, HAS_PRIME_UTIL
                                  ? Math::Prime::Util::mulint($P[-1], $P[-1])
                                  : Math::Prime::Util::GMP::mulint($P[-1], $P[-1]);
                                push @T, HAS_PRIME_UTIL
                                  ? Math::Prime::Util::subint($P[-1], 1)
                                  : Math::Prime::Util::GMP::subint($P[-1], "1");
                            }
                            $iphi =
                              HAS_PRIME_UTIL
                              ? Math::Prime::Util::mulint($iphi, $T[$bit])
                              : Math::Prime::Util::GMP::mulint($iphi, $T[$bit]);
                        }
                    }

                    my @terms;
                    for my $m (1 .. $i) {
                        next if $h[$i - $m] eq "0";
                        while (@J <= $m) {
                            push @J, HAS_PRIME_UTIL
                              ? Math::Prime::Util::mulint($J[-1], $pk)
                              : Math::Prime::Util::GMP::mulint($J[-1], $pk);
                        }
                        push @terms, HAS_PRIME_UTIL
                          ? Math::Prime::Util::mulint($J[$m], $h[$i - $m])
                          : Math::Prime::Util::GMP::mulint($J[$m], $h[$i - $m]);
                    }
                    my $sum =
                      HAS_PRIME_UTIL
                      ? Math::Prime::Util::vecsum(@terms)
                      : Math::Prime::Util::GMP::vecsum(@terms);
                    push @h, HAS_PRIME_UTIL
                      ? Math::Prime::Util::subint($iphi, $sum)
                      : Math::Prime::Util::GMP::subint($iphi, $sum);
                }
                $h[$e];
            };
        } grep { $_->[1] >= 2 } (HAS_PRIME_UTIL ? Math::Prime::Util::factor_exp($n) : _factor_exp($n))
    );
}

sub iphi_sum {
    my ($n, $j) = @_;

    $n = _big2uistr($$n) // return ZERO;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    if ($j == 0) {
        $n eq '0' and return ZERO;
        return ONE;
    }

    my $s = Math::Prime::Util::GMP::sqrtint($n);
    my $P =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::powerful_numbers(1, $n)
      : [map { $$_ } @{_set_int($n)->squarefull}];

    my @F = ();
    my @S = (0);

    foreach my $k (@$P) {
        my $t = _iphi_squarefull_aux($k, $j);
        push(@F, $t) if !($k > $s);
        push @S, HAS_PRIME_UTIL
          ? Math::Prime::Util::addint($S[-1], $t)
          : Math::Prime::Util::GMP::addint($S[-1], $t);
    }

    my $j_obj = bless \$j;

    my @terms;
    foreach my $i (0 .. $#F) {
        push @terms, Math::Prime::Util::GMP::mulint($F[$i], ${_set_int(Math::Prime::Util::GMP::divint($n, $P->[$i]))->phi_sum($j_obj)});
    }

    foreach my $k (1 .. $s) {
        my $t =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($n, $k)
          : Math::Prime::Util::GMP::divint($n, $k);
        my $c =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::powerful_count($t)
          : Math::Prime::Util::GMP::powerful_count($t);
        my $Jk =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::jordan_totient($j, $k)
          : Math::Prime::Util::GMP::jordan_totient($j, $k);
        push @terms, HAS_PRIME_UTIL
          ? Math::Prime::Util::mulint($Jk, $S[$c])
          : Math::Prime::Util::GMP::mulint($Jk, $S[$c]);
    }

    my $A = Math::Prime::Util::GMP::vecsum(@terms);
    my $B = Math::Prime::Util::GMP::mulint(${_set_int($s)->phi_sum($j_obj)}, $S[Math::Prime::Util::GMP::powerful_count($s)]);

    _set_int(Math::Prime::Util::GMP::subint($A, $B));
}

sub bphi {    # OEIS: A116550 -- bi-unitary analog of Euler's totient
    my ($n, $k) = @_;

    # Generalized bi-unitary analog of Jordan's totient function J_k(n).
    # Formula:
    #   a(n) = Sum_c w(c) * floor(n/c)^k

    my $n_str = _big2uistr($$n) // goto &nan;
    if ($n_str eq '1') {
        return ONE;
    }

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    my @factors =
      (HAS_PRIME_UTIL and $n_str < ULONG_MAX)
      ? Math::Prime::Util::factor_exp($n_str)
      : _factor_exp($n_str);

    # Generate choices for each prime factor: [1, p^e, p^{e+1}]
    my @choices;
    foreach my $f (@factors) {
        my ($p, $e) = @$f;

        my $pe  = Math::Prime::Util::GMP::powint($p, $e);
        my $pe1 = Math::Prime::Util::GMP::mulint($pe, $p);

        # Format: [ [value, weight], ... ]
        push @choices, [['1', 1], [$pe, -1], [$pe1, 1],];
    }

    # Sort to maximize DFS pruning (evaluate largest p^e limits first)
    @choices = sort { Math::Prime::Util::GMP::cmpint($b->[1][0], $a->[1][0]) } @choices;

    my @terms;

    # Depth-First Search closure
    sub {
        my ($idx, $current_c, $current_w) = @_;

        # Base case: All prime factors have been evaluated
        if ($idx == scalar(@choices)) {
            my $div = Math::Prime::Util::GMP::divint($n_str, $current_c);

            # Apply the Jordan Totient generalization: raise to the power of k
            if ($k != 1) {
                $div = Math::Prime::Util::GMP::powint($div, $k);
            }

            if ($current_w == -1) {
                push @terms, "-$div";
            }
            else {
                push @terms, $div;
            }
            return;
        }

        # Branching
        foreach my $choice (@{$choices[$idx]}) {
            my ($val, $wt) = @$choice;

            my $next_c;
            if ($val eq '1') {
                $next_c = $current_c;
            }
            else {
                $next_c = Math::Prime::Util::GMP::mulint($current_c, $val);
            }

            # Crucial Optimization: Prune search space early if c > n
            # Since choices are [1, p^e, p^{e+1}], if multiplying by the current
            # value exceeds 'n', subsequent choices in this loop will also exceed 'n'.
            last if Math::Prime::Util::GMP::cmpint($next_c, $n_str) > 0;

            __SUB__->($idx + 1, $next_c, $current_w * $wt);
        }
      }
      ->(0, '1', 1);

    my $total_sum = Math::Prime::Util::GMP::vecsum(@terms);
    _set_int($total_sum);
}

sub pillai {    # OEIS: A018804 -- Pillai's arithmetical function: Sum_{k=1..n} gcd(k, n).
    my ($n, $k) = @_;

    # Multiplicative with:
    #   a(p^e) = p^(e-1)*((p-1)*e + p) = p^(e-1) * ((1+e)*p - e)

    # Multiplicative formula for Sum_{k=1..n} gcd(n,k)^m:
    #   a(p^e) = p^(e-1)*(p^((m-1)*e+m) - p^((m-1)*e) - p + 1)/(p^(m-1)-1)

    # Multiplicative formula for Sum_{1 <= x_1, x_2, ..., x_k <= n} gcd(x_1, x_2, ..., x_k, n)^k:
    #   a(p^e) = (e - e/p^k + 1) * p^(k*e) = p^((e - 1) * k) * (p^k + e*(p^k - 1))

    $n = $$n;
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }

    $n eq '0' and return ZERO;
    $k == 0   and return ONE;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_set_ui($u, $p);
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k * ($e - 1));
        }
        else {
            Math::GMPz::Rmpz_set_str($u, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $u, $k * ($e - 1));
        }

        Math::GMPz::Rmpz_pow_ui($u, $u, $k) if ($k > 1);
        Math::GMPz::Rmpz_sub_ui($v, $u, 1);
        Math::GMPz::Rmpz_mul_ui($v, $v, $e);
        Math::GMPz::Rmpz_add($u, $u, $v);
        Math::GMPz::Rmpz_mul($t, $t, $u);

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub pillai_sum {
    my ($n, $k) = @_;

    # TODO: try to compute the sum, based on the Dirichlet convolution:
    #   pillai(n,k) = Sum_{d|n} mu(n/d) * d^k * tau(d)

    # See also:
    #   https://github.com/trizen/sidef-scripts/blob/master/Math/partial_sums_of_gcd-sum_function_fast.sf

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    my $k_obj = bless \$k;

    my $f = sub { $_[0]->ipow($k_obj)->mul($_[0]->tau) };
    my $g = sub { HAS_PRIME_UTIL ? Math::Prime::Util::moebius(${$_[0]}) : Math::Prime::Util::GMP::moebius(${$_[0]}) };

    my $F = sub { $_[0]->sigma_sum(ZERO, $k_obj) };
    my $G = sub { $_[0]->mertens };

    if ($k == 1) {
        $f = sub { $_[0]->mul($_[0]->tau) };
    }

    # Alternative convolution:
    if (HAS_PRIME_UTIL and $k == 1) {
        $f = sub { $_[0]->jordan_totient($k_obj) };
        $g = sub { $_[0]->ipow($k_obj) };

        $F = sub { $_[0]->phi_sum($k_obj) };
        $G = sub { $_[0]->faulhaber_sum($k_obj) };

        if ($k == 1) {
            $g = sub { $_[0] };
            $f = sub { $_[0]->euler_phi };
            $F = sub { $_[0]->phi_sum };
        }
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

sub prime_power_sigma {
    my ($n, $k) = @_;

    # Additive with:
    #   a(p^e, k) = (p^(k*(e+1)) - p^k) / (p^k - 1)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &prime_power_sigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my $r = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {
            ($k == 1)
              ? Math::GMPz::Rmpz_set_ui($u, $p)
              : Math::GMPz::Rmpz_ui_pow_ui($u, $p, $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($u, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($u, $u, $k) if ($k > 1);
        }

        Math::GMPz::Rmpz_pow_ui($t, $u, $e + 1);
        Math::GMPz::Rmpz_sub($t, $t, $u);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);
        Math::GMPz::Rmpz_divexact($t, $t, $u);
        Math::GMPz::Rmpz_add($r, $r, $t);
    }

    bless \$r;
}

sub prime_power_usigma {
    my ($n, $k) = @_;

    # Additive with:
    #   a(p^e, k) = p^(e*k)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &prime_power_usigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $r = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k * $e);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k * $e);
        }

        Math::GMPz::Rmpz_add($r, $r, $t);
    }

    bless \$r;
}

sub power_sigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = floor(e/k) + 1

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k)    // goto &nan } : 1;
    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _big2uistr($$n) // goto &nan } : (goto &nan);

    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    _set_int(
             Math::Prime::Util::GMP::vecprod(map  { Math::Prime::Util::GMP::divint($_->[1], $k) + 1 }
                                             grep { $_->[1] >= $k } _factor_exp($n))
            );
}

sub power_sigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e) = (p^(j*k*(1+floor(e/k))) - 1) / (p^(j*k) - 1), where e >= k.

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &power_sigma0;
    }

    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _big2uistr($$n) // goto &nan } : (goto &nan);
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        next if ($e < $k);

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k * $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k * $j);
        }

        Math::GMPz::Rmpz_pow_ui($u, $t, 1 + Math::Prime::Util::GMP::divint($e, $k));
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);
        Math::GMPz::Rmpz_divexact($u, $u, $t);

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($u)
            ? Math::GMPz::Rmpz_get_ui($u)
            : Math::GMPz::Rmpz_init_set($u)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub square_sigma0 {
    (TWO)->power_sigma0($_[0]);
}

sub square_sigma {
    (TWO)->power_sigma($_[0], $_[1]);
}

sub cube_sigma0 {
    (THREE)->power_sigma0($_[0]);
}

sub cube_sigma {
    (THREE)->power_sigma($_[0], $_[1]);
}

sub power_usigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = 2             if e == 0 (mod k)

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k)    // goto &nan } : 1;
    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _big2uistr($$n) // goto &nan } : (goto &nan);

    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($r, 0);
    Math::GMPz::Rmpz_setbit($r, scalar grep { $_->[1] % $k == 0 } _factor_exp($n));
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub power_usigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e) = p^(e*j) + 1, where e == 0 (mod k).

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &power_usigma0;
    }

    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _big2uistr($$n) // goto &nan } : (goto &nan);
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        $e % $k == 0 or next;

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e * $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$p", 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $e * $j);
        }

        Math::GMPz::Rmpz_add_ui($t, $t, 1);
        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub square_usigma0 {
    (TWO)->power_usigma0($_[0]);
}

sub square_usigma {
    (TWO)->power_usigma($_[0], $_[1]);
}

sub cube_usigma0 {
    (THREE)->power_usigma0($_[0]);
}

sub cube_usigma {
    (THREE)->power_usigma($_[0], $_[1]);
}

sub powerful_sigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = 1 if e < k
    #   a(p^e) = e - k + 2 if e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;

    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    _set_int(Math::Prime::Util::GMP::vecprod(map { $_->[1] - $k + 2 } grep { $_->[1] >= $k } _factor_exp($n)));
}

sub powerful_sigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e, k, j) = 1 if e < k
    #   a(p^e, k, j) = (p^(j*(e + 1)) - p^(j*k) + p^j - 1) / (p^j - 1) if e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &powerful_sigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();
    my @terms;

    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;

        next if ($e < $k);

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $j);
        }

        # Compute: (p^(j*(e + 1)) - p^(j*k) + p^j - 1) / (p^j - 1)
        Math::GMPz::Rmpz_pow_ui($u, $t, $e + 1);    # u = p^(j*(e+1))
        Math::GMPz::Rmpz_pow_ui($v, $t, $k);        # v = p^(j*k)
        Math::GMPz::Rmpz_sub($u, $u, $v);           # u = p^(j*(e+1)) - p^(j*k)
        Math::GMPz::Rmpz_add($u, $u, $t);           # u = p^(j*(e+1)) - p^(j*k) + p^j
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);         # u = p^(j*(e+1)) - p^(j*k) + p^j - 1
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);         # t = p^j - 1 (denominator)
        Math::GMPz::Rmpz_divexact($u, $u, $t);      # u = u / t

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($u)
            ? Math::GMPz::Rmpz_get_ui($u)
            : Math::GMPz::Rmpz_init_set($u)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub squarefull_sigma0 {
    (TWO)->powerful_sigma0($_[0]);
}

sub cubefull_sigma0 {
    (THREE)->powerful_sigma0($_[0]);
}

sub squarefull_sigma {
    (TWO)->powerful_sigma($_[0], $_[1]);
}

sub cubefull_sigma {
    (THREE)->powerful_sigma($_[0], $_[1]);
}

sub powerful_usigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = 1 if e < k
    #   a(p^e) = 2 if e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;
    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    # Each prime power with e >= k contributes exactly a factor of 2
    _set_int(Math::Prime::Util::GMP::powint(2, scalar grep { $_->[1] >= $k } _factor_exp($n)));
}

sub powerful_usigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e, k, j) = 1 if e < k
    #   a(p^e, k, j) = p^(j*e) + 1 if e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);
    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;
    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &powerful_usigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    my @terms;

    foreach my $pe (_factor_exp($n)) {
        my ($p, $e) = @$pe;
        next if ($e < $k);

        # Compute t = p^j
        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $j) if ($j > 1);
        }

        # Compute: p^(j*e) + 1  =>  (p^j)^e + 1  =>  t^e + 1
        Math::GMPz::Rmpz_pow_ui($u, $t, $e);    # u = t^e
        Math::GMPz::Rmpz_add_ui($u, $u, 1);     # u = u + 1

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($u)
            ? Math::GMPz::Rmpz_get_ui($u)
            : Math::GMPz::Rmpz_init_set($u)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub squarefull_usigma0 {
    (TWO)->powerful_usigma0($_[0]);
}

sub cubefull_usigma0 {
    (THREE)->powerful_usigma0($_[0]);
}

sub squarefull_usigma {
    (TWO)->powerful_usigma($_[0], $_[1]);
}

sub cubefull_usigma {
    (THREE)->powerful_usigma($_[0], $_[1]);
}

sub powerfree_sigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = min(e, k-1) + 1

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;

    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    _set_int(Math::Prime::Util::GMP::vecprod(map { ($_->[1] < $k) ? ($_->[1] + 1) : $k } _factor_exp($n)));
}

sub powerfree_sigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e) = (p^(j*(e+1)) - 1)/(p^j - 1), where e = min(e, k-1)

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &powerfree_sigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    my @terms;

    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        $e = $k - 1 if ($e >= $k);

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $j) if ($j > 1);
        }

        Math::GMPz::Rmpz_pow_ui($u, $t, $e + 1);
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);
        Math::GMPz::Rmpz_divexact($u, $u, $t);

        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($u)
            ? Math::GMPz::Rmpz_get_ui($u)
            : Math::GMPz::Rmpz_init_set($u)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

*squarefree_sigma0 = \&usigma0;

sub cubefree_sigma0 {
    (THREE)->powerfree_sigma0($_[0]);
}

sub squarefree_sigma {
    (TWO)->powerfree_sigma($_[0], $_[1]);
}

sub cubefree_sigma {
    (THREE)->powerfree_sigma($_[0], $_[1]);
}

sub powerfree_usigma0 {
    my ($k, $n) = @_;

    # Multiplicative with:
    #   a(p^e) = 2          # for e < k
    #   a(p^e) = 1          # for e >= k

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $n = _big2uistr($$n) // goto &nan;

    $k > 0 or return ZERO;
    $n eq '0' and return ZERO;

    state $r = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($r, 0);
    Math::GMPz::Rmpz_setbit($r, scalar grep { $_->[1] < $k } _factor_exp($n));
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub powerfree_usigma {
    my ($k, $n, $j) = @_;

    # Multiplicative with:
    #   a(p^e) = p^(e*j) + 1      # for e < k
    #   a(p^e) = 1                # for e >= k

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 1;

    $k > 0 or return ZERO;

    if ($j == 0) {
        goto &powerfree_usigma0;
    }

    $n = defined($n) ? do { ref($n) eq __PACKAGE__ or _valid(\$n); _big2uistr($$n) // goto &nan } : (goto &nan);
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @terms;
    foreach my $pe (_factor_exp($n)) {

        my ($p, $e) = @$pe;

        $e < $k or next;

        if (FAST_MODE and $p < ULONG_MAX) {

            if ($e == 1 and $j == 1) {    # optimization
                push @terms, $p + 1;
                next;
            }

            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e * $j);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $e * $j);
        }

        Math::GMPz::Rmpz_add_ui($t, $t, 1);
        push @terms,
          (
              Math::GMPz::Rmpz_fits_ulong_p($t)
            ? Math::GMPz::Rmpz_get_ui($t)
            : Math::GMPz::Rmpz_init_set($t)
          );
    }

    @terms || return ONE;
    bless \_binsplit(\@terms, \&__mul__);
}

sub squarefree_usigma0 {
    (TWO)->powerfree_usigma0($_[0]);
}

sub cubefree_usigma0 {
    (THREE)->powerfree_usigma0($_[0]);
}

sub squarefree_usigma {
    (TWO)->powerfree_usigma($_[0], $_[1]);
}

sub cubefree_usigma {
    (THREE)->powerfree_usigma($_[0], $_[1]);
}

sub prime_sigma {
    my ($n, $k) = @_;

    # Additive with:
    #   a(p^e, k) = p^k

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &prime_sigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();

    my $r = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pe (_factor_exp($n)) {

        my $p = $pe->[0];

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k);
        }

        Math::GMPz::Rmpz_add($r, $r, $t);
    }

    bless \$r;
}

sub prime_usigma0 {
    my ($n) = @_;
    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;
    _set_int(scalar grep { $_->[1] == 1 } _factor_exp($n));
}

sub prime_usigma {
    my ($n, $k) = @_;

    # Additive with:
    #   a(p,   k) = p^k
    #   a(p^e, k) = 0 for e>1

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;

    if ($k == 0) {
        goto &prime_usigma0;
    }

    $n = _big2uistr($$n) // goto &nan;
    $n eq '0' and return ZERO;

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $r = Math::GMPz::Rmpz_init_set_ui(0);

    foreach my $pe (grep { $_->[1] == 1 } _factor_exp($n)) {

        my $p = $pe->[0];

        if (FAST_MODE and $p < ULONG_MAX) {
            Math::GMPz::Rmpz_ui_pow_ui($t, $p, $k);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, $p, 10);
            Math::GMPz::Rmpz_pow_ui($t, $t, $k);
        }

        Math::GMPz::Rmpz_add($r, $r, $t);
    }

    bless \$r;
}

sub sigma0 {
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // goto &nan;

    (Math::GMPz::Rmpz_sgn($n) || return ZERO) > 0 or goto &nan;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return _set_int(Math::Prime::Util::divisor_sum(Math::GMPz::Rmpz_get_ui($n), 0));
    }

    $n = Math::GMPz::Rmpz_get_str($n, 10);

    if (CORE::length($n) < SPECIAL_FACTORS_MIN) {
        return _set_int(Math::Prime::Util::GMP::sigma($n, 0));
    }

    # Multiplicative with a(p^e) = e+1
    _set_int(Math::Prime::Util::GMP::vecprod(map { $_->[1] + 1 } _factor_exp($n)));
}

*d             = \&sigma0;
*divisor_count = \&sigma0;

sub sigma {
    my ($n, $k) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2si($$k) // goto &nan } : 1;

    if ($k == 0) {
        return $n->sigma0;
    }

    $n = $$n;

    if (FAST_MODE and HAS_PRIME_UTIL and $k == 1 and !ref($n) and $n > 0 and $n < (ULONG_MAX >> 3)) {
        return _set_int(Math::Prime::Util::divisor_sum($n));
    }

    $n = _sanitize_mpz($n) // goto &nan;
    (Math::GMPz::Rmpz_sgn($n) || return ZERO) > 0 or goto &nan;

    my $r;

    $n = Math::GMPz::Rmpz_get_str($n, 10);

    if (FAST_MODE and HAS_PRIME_UTIL and CORE::abs($k) == 1 and $n < (ULONG_MAX >> 3)) {
        $r = Math::Prime::Util::divisor_sum($n);
    }
    elsif (FAST_MODE and CORE::length($n) < SPECIAL_FACTORS_MIN) {
        $r = Math::Prime::Util::GMP::sigma($n, CORE::abs($k));
    }
    else {

        # Multiplicative with sigma_k(p^e) = ((p^((e+1)*k))-1)/(p^k-1)

        state $t = Math::GMPz::Rmpz_init_nobless();
        state $u = Math::GMPz::Rmpz_init_nobless();

        my @terms;

        foreach my $pp (_factor_exp($n)) {
            my ($p, $e) = @$pp;

            if (FAST_MODE and $p < ULONG_MAX) {

                if ($e == 1 and CORE::abs($k) == 1) {    # optimization
                    push @terms, $p + 1;
                    next;
                }

                Math::GMPz::Rmpz_ui_pow_ui($u, $p, CORE::abs($k));
            }
            else {
                Math::GMPz::Rmpz_set_str($u, "$p", 10);
                Math::GMPz::Rmpz_pow_ui($u, $u, CORE::abs($k)) if (CORE::abs($k) != 1);
            }

            Math::GMPz::Rmpz_pow_ui($t, $u, $e + 1);
            Math::GMPz::Rmpz_sub_ui($u, $u, 1);
            Math::GMPz::Rmpz_sub_ui($t, $t, 1);
            Math::GMPz::Rmpz_divexact($t, $t, $u);

            push @terms,
              (
                  Math::GMPz::Rmpz_fits_ulong_p($t)
                ? Math::GMPz::Rmpz_get_ui($t)
                : Math::GMPz::Rmpz_init_set($t)
              );
        }

        $r = (@terms ? _binsplit(\@terms, \&__mul__) : 1);
    }

    if ($k < 0) {    # Sum_{d|n} 1/d^k = sigma_k(n)/n^k
        return _set_int($r)->div(_set_int(Math::Prime::Util::GMP::powint($n, CORE::abs($k))));
    }

    _set_int($r);
}

*σ = \&sigma;

# Fast summation of the sigma_k(n) function for n=1..x
sub _sigma_sum {
    my ($x, $k) = @_;

    my $sx = Math::Prime::Util::GMP::sqrtint($x);

    if ($k == 0) {
        my @terms;
        for my $i (1 .. $sx) {
            push @terms, HAS_PRIME_UTIL
              ? Math::Prime::Util::divint($x, $i)
              : Math::Prime::Util::GMP::divint($x, $i);
        }
        my $total = Math::Prime::Util::GMP::vecsum(@terms);
        return Math::Prime::Util::GMP::subint(Math::Prime::Util::GMP::mulint(2, $total), Math::Prime::Util::GMP::mulint($sx, $sx));
    }

    my @terms;
    for my $i (1 .. $sx) {
        my $x_div_i =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::divint($x, $i)
          : Math::Prime::Util::GMP::divint($x, $i);

        push @terms, (HAS_PRIME_UTIL and $k <= 2)
          ? Math::Prime::Util::powersum($x_div_i, $k)
          : Math::Prime::Util::GMP::powersum($x_div_i, $k);

        my $ik = ($k == 1) ? $i : ($k == 2) ? Math::Prime::Util::GMP::mulint($i, $i) : Math::Prime::Util::GMP::powint($i, $k);
        push @terms, $ik * $x_div_i;
        $terms[-1] = Math::Prime::Util::GMP::mulint($ik, $x_div_i) if ($terms[-1] > ULONG_MAX);
    }

    my $total = Math::Prime::Util::GMP::vecsum(@terms);
    return Math::Prime::Util::GMP::subint($total, Math::Prime::Util::GMP::mulint($sx, Math::Prime::Util::GMP::powersum($sx, $k)));
}

sub sigma_sum {
    my ($n, $k, $j) = @_;

    $k = defined($k) ? do { ref($k) eq __PACKAGE__ or _valid(\$k); _any2ui($$k) // goto &nan } : 1;
    $j = defined($j) ? do { ref($j) eq __PACKAGE__ or _valid(\$j); _any2ui($$j) // goto &nan } : 0;

    if (FAST_MODE and $j == 0) {
        $n = _big2uistr($$n) // return ZERO;
        return _set_int(_sigma_sum($n, $k));
    }

    $k += $j;

    my $k_obj = bless \$k;
    my $j_obj = bless \$j;

    my $f = sub { $_[0]->ipow($k_obj) };
    my $g = sub { $_[0]->ipow($j_obj) };

    my $F = sub { _set_int(Math::Prime::Util::GMP::powersum(${$_[0]}, $k)) };
    my $G = sub { _set_int(Math::Prime::Util::GMP::powersum(${$_[0]}, $j)) };

    if ($k == 0) {
        $f = sub { 1 };
        $g = sub { 1 };
        $F = sub { $_[0] };
        $G = sub { $_[0] };
    }
    elsif ($k == 1) {
        $f = sub { $_[0] };
    }
    elsif ($k == 2) {
        $f = sub { $_[0]->sqr };
    }

    if ($j == 0) {
        $g = sub { 1 };
        $G = sub { $_[0] };
    }
    elsif ($j == 1) {
        $g = sub { $_[0] };
    }
    elsif ($j == 2) {
        $g = sub { $_[0]->sqr };
    }

    $n->dirichlet_hyperbola($f, $g, $F, $G);
}

sub tau_sum {
    $_[0]->sigma_sum(ZERO);
}

*sigma0_sum = \&tau_sum;

sub antidivisor_count {    # OEIS: A066272
    my ($n) = @_;

    $n = $$n;

    if (__cmp__($n, 2) <= 0) {
        return ZERO;
    }

    my $n2 = __add__($n, $n);

    my $w = (bless \__dec__($n2))->sigma0;
    my $x = (bless \__inc__($n2))->sigma0;
    my $y = (bless \$n)->remove(TWO)->sigma0;

    bless \__sub__(__add__(__add__($$w, $$x), $$y), 5);
}

*antidivisor_sigma0 = \&antidivisor_count;

sub antidivisor_sigma {    # OEIS: A066417 (k=1)
    my ($n, $k) = @_;

    # Generalization: computes the sum of d**k for all antidivisors d of n

    $n = $$n;

    if (__cmp__($n, 2) <= 0) {
        return ZERO;
    }

    # Default to k = 1 if not provided
    my $k_obj = defined($k) ? $k : ONE;
    my $n2    = __add__($n, $n);

    my $v = (bless \$n)->valuation(TWO);

    # Compute generalized sigma for odd boundaries
    my $x = (bless \__inc__($n2))->sigma($k_obj);
    my $y = (bless \__dec__($n2))->sigma($k_obj);

    # Compute generalized sigma for the even terms: sigma_k(m) * 2**(k * (v + 1))
    my $m_sigma = (bless \$n)->remove(TWO)->sigma($k_obj);
    my $v_inc   = bless \__inc__($$v);
    my $power   = $k_obj->mul($v_inc);
    my $two_pow = (TWO)->pow($power);
    my $z       = $m_sigma->mul($two_pow);

    # Compute the k-th powers of the terms that fall outside the range 1 < d < n
    my $t1 = (bless \__inc__($n2))->pow($k_obj);    # (2n+1)**k
    my $t2 = (bless \__dec__($n2))->pow($k_obj);    # (2n-1)**k
    my $t3 = (bless \$n2)->pow($k_obj);             # (2n)**k

    # Sum up the total invalid components to subtract
    my $excl = __add__(__add__(__add__($$t1, $$t2), $$t3), 2);

    # Final result: (x + y + z) - (t1 + t2 + t3 + 2)
    bless \__sub__(__add__(__add__($$x, $$y), $$z), $excl);
}

*antidivisor_sum = \&antidivisor_sigma;

sub aliquot {
    my ($n, $k) = @_;

    $n = $$n;

    if (FAST_MODE and HAS_NEW_PRIME_UTIL and !defined($k) and !ref($n) and $n < (ULONG_MAX >> 3)) {
        return _set_int(Math::Prime::Util::aliquot_sum($n));
    }

    $n = _sanitize_mpz($n) // goto &nan;
    (Math::GMPz::Rmpz_sgn($n) || return ZERO) > 0 or goto &nan;

    if (defined($k)) {
        return ((bless \$n)->sigma($k)->sub((bless \$n)->pow($k)));
    }

    my $s =
      (FAST_MODE and HAS_PRIME_UTIL and Math::GMPz::Rmpz_cmp_ui($n, ULONG_MAX >> 3) <= 0)
      ? Math::Prime::Util::divisor_sum(Math::GMPz::Rmpz_get_ui($n))
      : ${_set_int($n)->sigma};

    $s = Math::Prime::Util::GMP::subint($s, $n);

    _set_int($s);
}

*aliquot_sum  = \&aliquot;
*proper_sigma = \&aliquot;

sub proper_divisor_count {
    my ($n) = @_;
    $n->sigma0->dec->max(ZERO);
}

*proper_sigma0 = \&proper_divisor_count;

sub proper_divisors {
    my ($n) = @_;
    my $divisors = $n->divisors;
    $divisors->pop;
    $divisors;
}

sub abundancy_index {
    my ($n) = @_;
    $n->sigma->div($n);
}

*abundancy = \&abundancy_index;

sub is_deficient {
    my ($n) = @_;
    (__is_int__($$n) && __cmp__(${$n->sigma}, __mul__($$n, 2)) < 0) ? $TRUE : $FALSE;
}

sub is_abundant {
    my ($n) = @_;
    (__is_int__($$n) && __cmp__(${$n->sigma}, __mul__($$n, 2)) > 0) ? $TRUE : $FALSE;
}

sub is_primitive_abundant {
    my ($n) = @_;

    $n->is_abundant || return $FALSE;
    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        foreach my $pp (_factor_exp($n)) {
            my $t =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::divint($n, $pp->[0])
              : Math::Prime::Util::GMP::divint($n, $pp->[0]);
            (bless \$t)->is_abundant && return $FALSE;
        }
        return $TRUE;
    }

    $n = _any2mpz($n) // return $FALSE;

    my $t = Math::GMPz::Rmpz_init();
    foreach my $pp (_factor_exp($n)) {
        if (FAST_MODE and $pp->[0] < ULONG_MAX) {
            Math::GMPz::Rmpz_divexact_ui($t, $n, $pp->[0]);
        }
        else {
            Math::GMPz::Rmpz_set_str($t, "$pp->[0]", 10);
            Math::GMPz::Rmpz_divexact($t, $n, $t);
        }
        (bless \$t)->is_abundant && return $FALSE;
    }

    return $TRUE;
}

sub is_amicable {
    my ($n, $m) = @_;
    ref($m) eq __PACKAGE__ or _valid(\$m);
    if ($n->eq($m)) {
        return $FALSE;
    }
    (__is_int__($$n) && __is_int__($$m) && $n->aliquot->eq($m) && $m->aliquot->eq($n)) ? $TRUE : $FALSE;
}

sub is_zumkeller {
    my ($n) = @_;

    $n = _big2pistr($$n) // return $FALSE;

    _is_prob_prime($n) && return $FALSE;

    (
     (HAS_PRIME_UTIL and $n < ULONG_MAX)
     ? Math::Prime::Util::is_square($n)
     : Math::Prime::Util::GMP::is_square($n)
    )
      && return $FALSE;

    my $sigma =
      (HAS_PRIME_UTIL and $n < (ULONG_MAX >> 3))
      ? Math::Prime::Util::divisor_sum($n)
      : ${_set_int($n)->sigma};

    substr("$sigma", -1) & 1 and return $FALSE;

    Math::Prime::Util::GMP::cmpint($sigma, Math::Prime::Util::GMP::addint($n, $n),) < 0 and return $FALSE;

    substr($n, -1) & 1 and return $TRUE;    # conjecture?

    my @mpz_divs;
    foreach my $d (_divisors($n)) {
        unshift @mpz_divs, ($d < ULONG_MAX) ? $d : _any2mpz($d);
    }

    state $total = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_str($total, "$sigma", 10);

    state $target = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_fdiv_q_2exp($target, $total, 1);

    # Optimization for small inputs
    if (Math::GMPz::Rmpz_cmp_ui($target, 1e5) <= 0) {
        state $reachable = Math::GMPz::Rmpz_init_nobless();
        state $shifted   = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_set_ui($reachable, 1);
        foreach my $d (@mpz_divs) {
            Math::GMPz::Rmpz_mul_2exp($shifted, $reachable, $d);
            Math::GMPz::Rmpz_ior($reachable, $reachable, $shifted);
        }
        return (Math::GMPz::Rmpz_tstbit($reachable, Math::GMPz::Rmpz_get_ui($target)) ? $TRUE : $FALSE);
    }

    # Precompute suffix sums to fold impossible branches early
    my @suffix_sums;
    state $sum_so_far = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_set_ui($sum_so_far, 0);
    for (my $i = $#mpz_divs ; $i >= 0 ; $i--) {
        ref($mpz_divs[$i])
          ? Math::GMPz::Rmpz_add($sum_so_far, $sum_so_far, $mpz_divs[$i])
          : Math::GMPz::Rmpz_add_ui($sum_so_far, $sum_so_far, $mpz_divs[$i]);
        $suffix_sums[$i] = Math::GMPz::Rmpz_init_set($sum_so_far);
    }

    # Iterative Depth-First Search for the subset-sum target
    # Stack stores references to arrays: [ divisor_index, current_sum ]
    my @stack   = ([0, Math::GMPz::Rmpz_init_set_ui(0)]);
    my $max_idx = $#mpz_divs;

    my @mpz_obj = map { Math::GMPz::Rmpz_init() } 0 .. $max_idx;
    state $max_possible = Math::GMPz::Rmpz_init_nobless();

    while (@stack) {
        my $node        = pop @stack;
        my $idx         = $node->[0];
        my $current_sum = $node->[1];

        my $cmp = Math::GMPz::Rmpz_cmp($current_sum, $target);

        # Target reached
        if ($cmp == 0) {
            return $TRUE;
        }

        # Prune if we exceeded the target or ran out of divisors
        if ($cmp > 0 or $idx > $max_idx) {
            next;
        }

        # Branch pruning: if the current sum plus all remaining divisors can't reach the target
        Math::GMPz::Rmpz_add($max_possible, $current_sum, $suffix_sums[$idx]);
        if (Math::GMPz::Rmpz_cmp($max_possible, $target) < 0) {
            next;
        }

        # Branch 1: Exclude the current divisor
        push @stack, [$idx + 1, $current_sum];

        # Branch 2: Include the current divisor
        # Pushed last so it gets popped first (Greedy DFS)
        my $next_sum = $mpz_obj[$idx];
        ref($mpz_divs[$idx])
          ? Math::GMPz::Rmpz_add($next_sum, $current_sum, $mpz_divs[$idx])
          : Math::GMPz::Rmpz_add_ui($next_sum, $current_sum, $mpz_divs[$idx]);
        push @stack, [$idx + 1, $next_sum];
    }

    return $FALSE;
}

sub sopf {    # OEIS: A008472
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and HAS_NEW_PRIME_UTIL and !ref($n) and $n >= 0) {
        my $r = Math::Prime::Util::sopf($n);
        return bless \$r;
    }

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    my $s = Math::Prime::Util::GMP::vecsum(map { $_->[0] } _factor_exp($n));
    _set_int($s);
}

sub sopfr {    # OEIS: A001414
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and HAS_NEW_PRIME_UTIL and !ref($n) and $n >= 0) {
        my $r = Math::Prime::Util::sopfr($n);
        return bless \$r;
    }

    if (ref($n)) {
        $n = _big2uistr($n) // goto &nan;
    }
    else {
        $n < 0 and goto &nan;
    }
    my $s = Math::Prime::Util::GMP::vecsum(_factor($n));
    _set_int($s);
}

sub sopf_sum {
    my ($self) = @_;

    my $n_gmp = _any2mpz($$self) // return ZERO;

    if (Math::GMPz::Rmpz_cmp_ui($n_gmp, 2) < 0) {
        return ZERO;
    }

    if (FAST_MODE and HAS_PRIME_UTIL and $n_gmp < PRIMESUM_MIN) {

        my $f = sub { 1 };
        my $g = sub { Math::Prime::Util::is_prime(${$_[0]}) ? $_[0] : 0 };

        my $F = sub { $_[0] };
        my $G = sub { Math::Prime::Util::sum_primes(${$_[0]}) };

        return ((bless \$n_gmp)->dirichlet_hyperbola($f, $g, $F, $G));
    }

    my $m_gmp = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sqrt($m_gmp, $n_gmp);
    my $m = Math::GMPz::Rmpz_get_ui($m_gmp);

    my @S_small;
    my @S_large;

    my $v_gmp = Math::GMPz::Rmpz_init();
    for my $v (1 .. $m) {
        my $res = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_set_ui($v_gmp, $v);
        Math::GMPz::Rmpz_add_ui($res, $v_gmp, 1);
        Math::GMPz::Rmpz_mul($res, $res, $v_gmp);
        Math::GMPz::Rmpz_tdiv_q_2exp($res, $res, 1);
        Math::GMPz::Rmpz_sub_ui($res, $res, 1);
        $S_small[$v] = $res;
    }

    for my $k (1 .. $m) {
        my $res = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_tdiv_q_ui($v_gmp, $n_gmp, $k);
        Math::GMPz::Rmpz_add_ui($res, $v_gmp, 1);
        Math::GMPz::Rmpz_mul($res, $res, $v_gmp);
        Math::GMPz::Rmpz_tdiv_q_2exp($res, $res, 1);
        Math::GMPz::Rmpz_sub_ui($res, $res, 1);
        $S_large[$k] = $res;
    }

    my $scratch   = Math::GMPz::Rmpz_init();
    my $k_max_gmp = Math::GMPz::Rmpz_init();
    my $p2        = Math::GMPz::Rmpz_init();

    foreach my $p_ui (@{_primes(2, $m)}) {
        Math::GMPz::Rmpz_set_ui($p2, $p_ui);
        Math::GMPz::Rmpz_mul_ui($p2, $p2, $p_ui);

        Math::GMPz::Rmpz_tdiv_q($k_max_gmp, $n_gmp, $p2);
        my $k_max = Math::GMPz::Rmpz_get_ui($k_max_gmp);
        $k_max = $m if $k_max > $m;

        my $sp_minus_1 = $S_small[$p_ui - 1];
        my $kp         = $p_ui;

        foreach my $k (1 .. $k_max) {
            my $sw;
            if ($kp <= $m) {
                $sw = $S_large[$kp];
            }
            else {
                Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $kp);
                $sw = $S_small[Math::GMPz::Rmpz_get_ui($scratch)];
            }

            Math::GMPz::Rmpz_sub($scratch, $sw, $sp_minus_1);
            Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
            Math::GMPz::Rmpz_sub($S_large[$k], $S_large[$k], $scratch);
            $kp += $p_ui;
        }

        for (my $v = $m ; $v >= $p2 ; $v--) {
            my $sw = $S_small[CORE::int($v / $p_ui)];
            Math::GMPz::Rmpz_sub($scratch, $sw, $sp_minus_1);
            Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
            Math::GMPz::Rmpz_sub($S_small[$v], $S_small[$v], $scratch);
        }
    }

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);
    for my $k (1 .. $m) {
        Math::GMPz::Rmpz_add($sum, $sum, $S_large[$k]);
    }

    Math::GMPz::Rmpz_mul_ui($scratch, $S_large[$m], $m);
    Math::GMPz::Rmpz_sub($sum, $sum, $scratch);

    Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $m);
    my $limit = Math::GMPz::Rmpz_get_ui($scratch);

    foreach my $p_ui (@{_primes(2, $limit)}) {
        Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $p_ui);
        Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
        Math::GMPz::Rmpz_add($sum, $sum, $scratch);
    }

    bless \$sum;
}

sub sopfr_sum {
    my ($self) = @_;

    my $n_gmp = _any2mpz($$self) // return ZERO;

    if (Math::GMPz::Rmpz_cmp_ui($n_gmp, 2) < 0) {
        return ZERO;
    }

    my $m_gmp = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sqrt($m_gmp, $n_gmp);
    my $m = Math::GMPz::Rmpz_get_ui($m_gmp);

    my @S_small;
    my @S_large;

    my $v_gmp = Math::GMPz::Rmpz_init();
    for my $v (1 .. $m) {
        my $res = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_set_ui($v_gmp, $v);
        Math::GMPz::Rmpz_add_ui($res, $v_gmp, 1);
        Math::GMPz::Rmpz_mul($res, $res, $v_gmp);
        Math::GMPz::Rmpz_tdiv_q_2exp($res, $res, 1);
        Math::GMPz::Rmpz_sub_ui($res, $res, 1);
        $S_small[$v] = $res;
    }

    for my $k (1 .. $m) {
        my $res = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_tdiv_q_ui($v_gmp, $n_gmp, $k);
        Math::GMPz::Rmpz_add_ui($res, $v_gmp, 1);
        Math::GMPz::Rmpz_mul($res, $res, $v_gmp);
        Math::GMPz::Rmpz_tdiv_q_2exp($res, $res, 1);
        Math::GMPz::Rmpz_sub_ui($res, $res, 1);
        $S_large[$k] = $res;
    }

    my $scratch   = Math::GMPz::Rmpz_init();
    my $k_max_gmp = Math::GMPz::Rmpz_init();
    my $p2        = Math::GMPz::Rmpz_init();

    my $m_primes = _primes(2, $m);

    foreach my $p_ui (@$m_primes) {
        Math::GMPz::Rmpz_set_ui($p2, $p_ui);
        Math::GMPz::Rmpz_mul_ui($p2, $p2, $p_ui);

        Math::GMPz::Rmpz_tdiv_q($k_max_gmp, $n_gmp, $p2);
        my $k_max = Math::GMPz::Rmpz_get_ui($k_max_gmp);
        $k_max = $m if $k_max > $m;

        my $sp_minus_1 = $S_small[$p_ui - 1];
        my $kp         = $p_ui;

        foreach my $k (1 .. $k_max) {
            my $sw;
            if ($kp <= $m) {
                $sw = $S_large[$kp];
            }
            else {
                Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $kp);
                $sw = $S_small[Math::GMPz::Rmpz_get_ui($scratch)];
            }

            Math::GMPz::Rmpz_sub($scratch, $sw, $sp_minus_1);
            Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
            Math::GMPz::Rmpz_sub($S_large[$k], $S_large[$k], $scratch);
            $kp += $p_ui;
        }

        for (my $v = $m ; $v >= $p2 ; $v--) {
            my $sw = $S_small[CORE::int($v / $p_ui)];
            Math::GMPz::Rmpz_sub($scratch, $sw, $sp_minus_1);
            Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
            Math::GMPz::Rmpz_sub($S_small[$v], $S_small[$v], $scratch);
        }
    }

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);
    for my $k (1 .. $m) {
        Math::GMPz::Rmpz_add($sum, $sum, $S_large[$k]);
    }

    Math::GMPz::Rmpz_mul_ui($scratch, $S_large[$m], $m);
    Math::GMPz::Rmpz_sub($sum, $sum, $scratch);

    Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $m);
    my $limit = Math::GMPz::Rmpz_get_ui($scratch);

    foreach my $p_ui (@{_primes(2, $limit)}) {
        Math::GMPz::Rmpz_tdiv_q_ui($scratch, $n_gmp, $p_ui);
        Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
        Math::GMPz::Rmpz_add($sum, $sum, $scratch);
    }

    my $p_pow = Math::GMPz::Rmpz_init();
    foreach my $p_ui (@$m_primes) {

        Math::GMPz::Rmpz_set_ui($p_pow, $p_ui);
        Math::GMPz::Rmpz_mul_ui($p_pow, $p_pow, $p_ui);

        while (Math::GMPz::Rmpz_cmp($p_pow, $n_gmp) <= 0) {
            Math::GMPz::Rmpz_tdiv_q($scratch, $n_gmp, $p_pow);
            Math::GMPz::Rmpz_mul_ui($scratch, $scratch, $p_ui);
            Math::GMPz::Rmpz_add($sum, $sum, $scratch);
            Math::GMPz::Rmpz_mul_ui($p_pow, $p_pow, $p_ui);
        }
    }

    bless \$sum;
}

sub factor_map {
    my ($n, $block) = @_;

    $n = _big2pistr($$n) // return _array();

    my @array;
    foreach my $pk (_factor_exp($n)) {

        my ($p, $k) = @$pk;

        $p = _set_int($p);
        $k = _set_int($k);

        push @array, $block->run($p, $k);
    }

    _array(\@array);
}

sub _divisors_lazy {
    my ($n, $callback) = @_;

    $n = $$n;

    if (ref($n)) {
        $n = _big2pistr($n) // return;
    }
    else {
        $n >= 1 or return;
    }

    my @factors = _factor_exp($n);

    if (!@factors) {
        $callback->(ONE);
        return;
    }

    my @primes    = map { $_->[0] } @factors;
    my @exps      = map { $_->[1] } @factors;
    my $max_depth = scalar(@primes);

    if (FAST_MODE and $n < ULONG_MAX) {    # native divisors
        return sub {
            my ($idx, $prod) = @_;
            if ($idx == $max_depth) {
                $callback->(bless \$prod);
                return;
            }
            my $p = $primes[$idx];
            my $e = $exps[$idx];
            my $m = 1;
            foreach my $i (1 .. $e + 1) {
                __SUB__->($idx + 1, $prod * $m);
                $m *= $p;
            }
          }
          ->(0, 1);
    }

    @primes = map { Math::GMPz::Rmpz_init_set_str($_, 10) } @primes;

    sub {
        my ($idx, $prod) = @_;
        if ($idx == $max_depth) {
            my $r = Math::GMPz::Rmpz_init_set($prod);
            $callback->(bless \$r);
            return;
        }
        my $p = $primes[$idx];
        my $e = $exps[$idx];
        my $m = Math::GMPz::Rmpz_init_set_ui(1);
        my $t = Math::GMPz::Rmpz_init();
        foreach my $i (1 .. $e + 1) {
            Math::GMPz::Rmpz_mul($t, $prod, $m);
            __SUB__->($idx + 1, $t);
            Math::GMPz::Rmpz_mul($m, $m, $p);
        }
      }
      ->(0, Math::GMPz::Rmpz_init_set_ui(1));
}

sub divisor_each {
    my ($n, $block) = @_;
    _divisors_lazy($n, $block);
}

*divisors_each = \&divisor_each;

sub divisor_map {
    my ($n, $block) = @_;

    $n = _big2pistr($$n) // return _array();

    my @array;
    foreach my $divisor (_divisors($n)) {
        push @array, $block->run(_set_int($divisor));
    }

    _array(\@array);
}

*divisors_map = \&divisor_map;

sub divisor_sum {
    my ($n, $block) = @_;
    $block // return $n->sigma;
    $n->divisor_map($block)->sum;
}

*divisors_sum = \&divisor_sum;

sub divisor_prod {
    my ($n, $block) = @_;
    $block // return $n->divisors->prod;
    $n->divisor_map($block)->prod;
}

*divisors_prod = \&divisor_prod;

sub factor_sum {
    my ($n, $block) = @_;
    $block // return $n->sopfr;
    $n->factor_map($block)->sum;
}

*factors_sum = \&factor_sum;

sub factor_prod {
    my ($n, $block) = @_;
    $block // return $n;
    $n->factor_map($block)->prod;
}

*factors_prod = \&factor_prod;

sub _partition_count_k_parts {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2ui($$n) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    return ONE  if ($k == $n);
    return ZERO if ($k > $n or $k == 0);
    return ONE  if ($k == 1);

    # Generating function in PARI/GP
    if ($USE_PARI_GP and $n > 1000 and $k < 500) {
        my $diff = $n - $k;
        my $code = "iferr(polcoeff(1/prod(i=1, $k, 1-x^i) + O(x^($diff+1)), $diff), E, \"\")";
        if (my $res = _execute_pari_gp($code)) {
            if ($res =~ /^\d+$/) {
                return _set_int($res);
            }
        }
    }

    # O(n*k) DP array using GMPz
    # P(n,k) = P(n-1, k-1) + P(n-k, k)
    my @dp;
    for my $i (0 .. $n) {
        $dp[$i] = [map { Math::GMPz::Rmpz_init_set_ui(0) } 0 .. $k];
    }
    Math::GMPz::Rmpz_set_ui($dp[0][0], 1);

    for my $i (1 .. $n) {
        for my $j (1 .. ($i < $k ? $i : $k)) {
            Math::GMPz::Rmpz_add($dp[$i][$j], $dp[$i - 1][$j - 1], $dp[$i - $j][$j]);
        }
    }

    my $r = $dp[$n][$k];
    bless \$r;
}

sub _partition_count_for_set {
    my ($n, $set) = @_;

    $n = _any2ui($$n) // return ZERO;

    my @S = map { _any2ui($$_) // return ZERO } @{$set};
    @S = sort { $a <=> $b } grep { $_ > 0 } @S;

    my %seen;
    @S = grep { !$seen{$_}++ } @S;

    return ONE if $n == 0;
    return ZERO unless @S;

    # PARI/GP Generating Functions for heavy values
    if ($n > 10_000 and $USE_PARI_GP) {

        # 1 / Prod(1 - x^c)
        my $prod_str = join(" * ", map { "(1 - x^$_)" } @S);
        my $code     = "iferr(polcoeff(1/($prod_str) + O(x^($n+1)), $n), E, \"\")";
        if (my $res = _execute_pari_gp($code)) {
            if ($res =~ /^\d+$/) {
                return _set_int($res);
            }
        }
    }

    # Standard DP array using GMPz
    my @dp = (Math::GMPz::Rmpz_init_set_ui(1), map { Math::GMPz::Rmpz_init_set_ui(0) } 1 .. $n);

    for my $coin (@S) {
        for my $i ($coin .. $n) {
            Math::GMPz::Rmpz_add($dp[$i], $dp[$i], $dp[$i - $coin]);
        }
    }

    my $r = $dp[$n];
    bless \$r;
}

sub partition_count {
    my ($n, $argument) = @_;

    if (defined($argument)) {
        if (ref($argument) eq __PACKAGE__) {
            return _partition_count_k_parts($n, $argument);
        }
        return _partition_count_for_set($n, $argument);
    }

    $n = _big2uistr($$n) // goto &nan;

    if ($n >= 1e3 and $USE_PARI_GP) {
        if (my $res = _execute_pari_gp("numbpart($n)")) {
            return _set_int($res);
        }
    }

    _set_int(Math::Prime::Util::GMP::partitions($n));
}

*partition_number = \&partition_count;

sub is_primitive_root {
    my ($x, $y) = @_;
    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    (__is_int__($x) && __is_int__($y) && Math::Prime::Util::GMP::is_primitive_root(_big2uistr($x) // (return $FALSE), _big2uistr($y) // (return $FALSE)))
      ? $TRUE
      : $FALSE;
}

sub _sieve_powerful {
    my ($from, $to, $k) = @_;

    my @powerful;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($to)) {

        $from = Math::GMPz::Rmpz_get_ui($from);
        $to   = Math::GMPz::Rmpz_get_ui($to);

        sub {
            my ($m, $r) = @_;

            my $lo = 1;
            my $hi = Math::Prime::Util::rootint(
                                                (
                                                 HAS_PRIME_UTIL
                                                 ? Math::Prime::Util::divint($to, $m)
                                                 : Math::Prime::Util::GMP::divint($to, $m)
                                                ),
                                                $r
                                               );

            if ($r <= $k) {

                if ($from > $m) {
                    my $t =
                      HAS_PRIME_UTIL
                      ? Math::Prime::Util::cdivint($from, $m)
                      : Math::Prime::Util::GMP::cdivint($from, $m);
                    $lo = Math::Prime::Util::rootint($t, $r);
                    $lo++
                      if (
                          (
                           HAS_PRIME_UTIL
                           ? Math::Prime::Util::powint($lo, $r)
                           : Math::Prime::Util::GMP::powint($lo, $r)
                          ) != $t
                         );
                }

                foreach my $v ($lo .. $hi) {
                    push @powerful,
                      $m * (
                            HAS_PRIME_UTIL
                            ? Math::Prime::Util::powint($v, $r)
                            : Math::Prime::Util::GMP::powint($v, $r)
                           );
                }

                return;
            }

            foreach my $v ($lo .. $hi) {

                Math::Prime::Util::is_square_free($v) or next;
                Math::Prime::Util::gcd($m, $v) == 1   or next;

                __SUB__->(
                          $m * (
                                HAS_PRIME_UTIL
                                ? Math::Prime::Util::powint($v, $r)
                                : Math::Prime::Util::GMP::powint($v, $r)
                               ),
                          $r - 1
                         );
            }
          }
          ->(1, 2 * $k - 1);
    }
    else {
        state $t = Math::GMPz::Rmpz_init_nobless();

        sub {
            my ($m, $r) = @_;

            Math::GMPz::Rmpz_div($t, $to, $m);
            Math::GMPz::Rmpz_root($t, $t, $r);

            Math::GMPz::Rmpz_fits_ulong_p($t) || die "Too large value!";

            my $lo = 1;
            my $hi = Math::GMPz::Rmpz_get_ui($t);

            if ($r <= $k) {

                if (Math::GMPz::Rmpz_cmp($from, $m) > 0) {
                    Math::GMPz::Rmpz_cdiv_q($t, $from, $m);
                    my $exact = Math::GMPz::Rmpz_root($t, $t, $r);
                    $lo = Math::GMPz::Rmpz_get_ui($t) + ($exact ? 0 : 1);
                }

                foreach my $v ($lo .. $hi) {
                    Math::GMPz::Rmpz_ui_pow_ui($t, $v, $r);
                    Math::GMPz::Rmpz_mul($t, $t, $m);
                    push @powerful,
                      (
                          Math::GMPz::Rmpz_fits_ulong_p($t)
                        ? Math::GMPz::Rmpz_get_ui($t)
                        : Math::GMPz::Rmpz_init_set($t)
                      );
                }

                return;
            }

            foreach my $v ($lo .. $hi) {

                (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($v) : Math::Prime::Util::GMP::moebius($v)) or next;
                Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $v) == 1                                        or next;

                Math::GMPz::Rmpz_ui_pow_ui($t, $v, $r);
                Math::GMPz::Rmpz_mul($t, $t, $m);

                __SUB__->(Math::GMPz::Rmpz_init_set($t), $r - 1);
            }
          }
          ->($ONE, 2 * $k - 1);
    }

    @powerful = sort { $a <=> $b } @powerful;
    return \@powerful;
}

# Array of k-powerful numbers in the range [from, to]
sub powerful {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @powerful = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_powerful($from, $to, $k)};
#>>>

    _array(\@powerful);
}

sub squarefull {
    (TWO)->powerful(@_);
}

sub cubefull {
    (THREE)->powerful(@_);
}

sub powerful_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    my $step_value = $TEN**(6 * $k);    # TODO: improve this value
    _generic_each($from, $to, $block, sub { $step_value }, sub { _sieve_powerful($_[0], $_[1], $k) });
}

*each_powerful = \&powerful_each;

sub squarefull_each {
    (TWO)->powerful_each(@_);
}

*each_squarefull = \&squarefull_each;

sub cubefull_each {
    (THREE)->powerful_each(@_);
}

*each_cubefull = \&cubefull_each;

sub nth_powerful {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 1 or goto &nan;
    }
    else {
        $k = 2;
    }

    my $k_obj = bless \$k;
    my $n_obj = $n;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) > 0 or do {
        return ZERO if (Math::GMPz::Rmpz_sgn($n) == 0);    # not k-powerful, but...
        goto &nan;
    };

    if ($k == 1) {
        return _set_int($n);
    }

    my $min = Math::GMPz::Rmpz_init_set_ui(1);
    my $max = Math::GMPz::Rmpz_init_set($n);

    Math::GMPz::Rmpz_mul($max, $max, $n);

    while (__cmp__(${$k_obj->powerful_count(bless \$max)}, $n) < 0) {
        Math::GMPz::Rmpz_set($min, $max);
        Math::GMPz::Rmpz_mul_ui($max, $max, $k);
    }

    my $v     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init();

    while (1) {
        Math::GMPz::Rmpz_add($v, $min, $max);
        Math::GMPz::Rmpz_div_2exp($v, $v, 1);

        $count =
          (HAS_PRIME_UTIL && Math::GMPz::Rmpz_fits_ulong_p($v))
          ? Math::GMPz::Rmpz_init_set_ui(Math::Prime::Util::powerful_count(Math::GMPz::Rmpz_get_ui($v), $k))
          : ${$k_obj->powerful_count(bless \$v)};

        my $cmp = __cmp__($count, $n);

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($max, $v, 1);
        }
        elsif ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($min, $v, 1);
        }
        else {
            last;
        }
    }

    $k_obj->powerful((bless \$min), (bless \$v))->last;
}

sub nth_squarefull {
    my ($n) = @_;
    $n->nth_powerful(TWO);
}

sub nth_cubefull {
    my ($n) = @_;
    $n->nth_powerful(THREE);
}

sub next_powerful {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
    }
    else {
        $k = TWO;
    }

    $k->powerful_count($n)->inc->nth_powerful($k);
}

sub next_squarefull {
    my ($n) = @_;
    $n->next_powerful(TWO);
}

sub next_cubefull {
    my ($n) = @_;
    $n->next_powerful(THREE);
}

sub prev_powerful {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
    }
    else {
        $k = TWO;
    }

    my $count = $k->powerful_count($n);
    $count = $count->dec if $n->is_powerful($k);
    $count->nth_powerful($k);
}

sub prev_squarefull {
    my ($n) = @_;
    $n->prev_powerful(TWO);
}

sub prev_cubefull {
    my ($n) = @_;
    $n->prev_powerful(THREE);
}

# =============================================================================
# Helper: Strong Fermat condition check for a prime (power) p.
#
# Returns true iff:
#   v2(p-1) > k_exp   AND
#   base^((p-1) >> (v2(p-1) - k_exp))  ≡  congr  (mod p)
#
# Dispatches to Math::Prime::Util or Math::Prime::Util::GMP as available.
# p must fit in a native unsigned long (always the case when called from the
# sieve generator, where p is iterated with next_prime from a small lower
# bound).
# =============================================================================
sub _strong_fermat_check {
    my ($p, $base, $k_exp, $congr) = @_;

    my $val =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::valuation($p - 1, 2)
      : Math::Prime::Util::GMP::valuation($p - 1, 2);

    return 0 unless $val > $k_exp;

    my $pm =
      HAS_PRIME_UTIL
      ? Math::Prime::Util::powmod($base, ($p - 1) >> ($val - $k_exp), $p)
      : Math::Prime::Util::GMP::powmod($base, ($p - 1) >> ($val - $k_exp), $p);

    $pm == ($congr % $p);
}

# =============================================================================
# Brute-force sieve for k-omega primes in [$from, $to].
# Used when the interval is so small that iterating every integer is faster
# than the recursive generator: |B - A| < B^(1/(k+1)).
# =============================================================================
sub _sieve_small_range {
    my ($from, $to, $k, $fermat, $strong) = @_;

    my $v        = Math::GMPz::Rmpz_init_set($from);
    my $o        = bless \$v;                          # Sidef::Types::Number::Number
    my $k_obj    = bless \$k;
    my $base_obj = bless \$fermat;                     # only used when $fermat is truthy

    my @arr;
    for (; Math::GMPz::Rmpz_cmp($v, $to) <= 0 ; Math::GMPz::Rmpz_add_ui($v, $v, 1)) {
        if ($fermat) {
            next unless $strong
              ? $o->is_strong_fermat_psp($base_obj)
              : $o->is_fermat_psp($base_obj);
        }
        if ($k == 1) {
            $o->is_prime_power || next;
        }
        else {
            $o->is_omega_prime($k_obj) || next;
        }
        push @arr, Math::GMPz::Rmpz_init_set($v);
    }
    return \@arr;
}

# =============================================================================
# Sieve for k-omega (Fermat) primes using native unsigned-long arithmetic.
# Pre-conditions: HAS_PRIME_UTIL is true AND $to fits in an unsigned long.
# =============================================================================
sub _sieve_omega_ulong {
    my ($from, $to, $k, $fermat, $strong) = @_;

    my $A = Math::GMPz::Rmpz_get_ui($from);
    my $B = Math::GMPz::Rmpz_get_ui($to);

    # Raise A to at least the k-th primorial so the smallest possible
    # product of k distinct primes is always in range.
    $A = Math::Prime::Util::vecmax($A, Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    return [] if $A > $B;

    my @omega_primes;
    my %znorder;    # memoised multiplicative orders: p -> ord_{p}(fermat)

    my $generator;
    $generator = sub {
        my ($m, $lo, $j, %args) = @_;

        my $hi = Math::Prime::Util::rootint(Math::Prime::Util::divint($B, $m), $j,);
        return if $lo > $hi;

        # ------------------------------------------------------------------
        # Base case j == 1: choose the final prime factor.
        # ------------------------------------------------------------------
        if ($j == 1) {
            my $lambda = $args{lambda};

            # Fast path: no Fermat filter, or lambda == 1 (no congruence
            # constraint on p beyond being prime).
            if (!$fermat or $lambda == 1) {
                for (my $p = Math::Prime::Util::next_prime($lo - 1) ; $p <= $hi ; $p = Math::Prime::Util::next_prime($p)) {
                    next if ($fermat and $fermat % $p == 0);
                    next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

                    # When m==1 and we have a Fermat filter, start from p^2
                    # so every candidate n = p^e has at least two prime
                    # factors counted with multiplicity.
                    my $v = ($fermat and $m == 1) ? $p * $p : $m * $p;
                    for (; $v - 1 < $B ; $v *= $p) {
                        next unless $v >= $A;
                        if ($fermat) {
                            last unless Math::Prime::Util::powmod($fermat, $v - 1, $v) == 1;
                        }
                        push @omega_primes, $v;
                    }
                }
                return;
            }

            # Fermat path with lambda > 1: valid prime powers must satisfy
            # p ≡ m^{-1} (mod lambda).
            my $t = Math::Prime::Util::invmod($m, $lambda);
            return                                                        if $t > $hi;
            $t += Math::Prime::Util::cdivint($lo - $t, $lambda) * $lambda if $t < $lo;
            return                                                        if $t > $hi;

            for (my $p = $t ; $p <= $hi ; $p += $lambda) {
                next
                  unless Math::Prime::Util::is_prime_power($p)
                  && Math::Prime::Util::gcd($m,      $p) == 1
                  && Math::Prime::Util::gcd($fermat, $p) == 1;
                next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

                my $v = $m * $p;
                next unless $v >= $A;

                # k==1 composites that happen to be prime are excluded.
                next if ($k == 1 and Math::Prime::Util::is_prime($v));
                next unless (($v - 1) % ($znorder{$p} //= Math::Prime::Util::znorder($fermat, $p)) == 0);
                push @omega_primes, $v;
            }
            return;
        }

        # ------------------------------------------------------------------
        # Recursive case j > 1: choose one prime factor and recurse.
        # ------------------------------------------------------------------
        my $lambda = $args{lambda};

        for (my ($p, $r, $L) = ($lo) ; $p <= $hi ; $p = $r) {
            $r = Math::Prime::Util::next_prime($p);
            next if ($fermat and $fermat % $p == 0);
            next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

            my $z;
            if ($fermat) {
                $z = ($znorder{$p} //= Math::Prime::Util::znorder($fermat, $p));
                next unless Math::Prime::Util::gcd($m, $z) == 1;
                $L = Math::Prime::Util::lcm($lambda, $z);
            }

            # Iterate over prime powers of p: p, p^2, p^3, ...
            for (my ($pk, $v) = ($p, $m * $p) ; $v - 1 < $B ; ($pk, $v) = ($pk * $p, $v * $p)) {

                # For p^e with e >= 2, verify the Fermat order condition
                # extends to the higher power before recursing.
                last if ($fermat and $pk > $p and Math::Prime::Util::powmod($fermat, $z, $pk) != 1);

                if ($v * $r - 1 < $B) {
                    $generator->($v, $r, $j - 1, %args, (defined $L ? (lambda => $L) : ()));
                }
            }
        }
    };

    # ------------------------------------------------------------------
    # Kick off the generator.
    # ------------------------------------------------------------------
    if ($strong) {

        # Case where base^d ≡ 1 (mod p), d = odd part of p-1.
        $generator->(1, 2, $k, lambda => 1, k_exp => 0, congr => 1);

        # Cases where base^(d·2^v) ≡ -1 (mod p), for v = 0 .. floor(log2 B).
        for my $v (0 .. Math::Prime::Util::logint($B, 2)) {
            $generator->(1, 2, $k, lambda => 1, k_exp => $v, congr => -1);
        }
    }
    else {
        $generator->(1, 2, $k, ($fermat ? (lambda => 1) : ()));
    }

    undef %znorder;
    undef $generator;    # break circular reference

    @omega_primes = sort { $a <=> $b } @omega_primes;

    $fermat
      ? [List::Util::uniq(@omega_primes)]
      : \@omega_primes;
}

# =============================================================================
# Sieve for k-omega (Fermat) primes using GMP big-integer arithmetic.
# Used when $to does not fit in an unsigned long.
#
# Design notes on GMPz allocation strategy
# -----------------------------------------
# * $su / $sv are scratch variables captured by the closure and reused across
#   every j==1 base-case call.  This is safe because j==1 never recurses.
# * The j>1 recursive case allocates its own per-frame $lv / $lu / $lcm so
#   that recursive frames do not alias each other's loop variables.
# * $lcm is passed to the recursive call by reference (no copy).  The callee
#   returns before the caller overwrites $lcm for the next prime, so there is
#   no aliasing hazard in single-threaded Perl.
# =============================================================================
sub _sieve_omega_gmp {
    my ($from, $to, $k, $fermat, $strong) = @_;

    my $A = Math::GMPz::Rmpz_init_set($from);
    my $B = Math::GMPz::Rmpz_init_set($to);

    # Raise A to at least the k-th primorial.
    {
        my $primorial = _cached_pn_primorial($k);
        if (Math::GMPz::Rmpz_cmp($primorial, $A) > 0) {
            Math::GMPz::Rmpz_set($A, $primorial);
        }
    }
    return [] if Math::GMPz::Rmpz_cmp($A, $B) > 0;

    # Closure-captured scratch variables (j==1 base case only; never aliased
    # by recursive j>1 frames).
    my $su = Math::GMPz::Rmpz_init();
    my $sv = Math::GMPz::Rmpz_init();
    my $sw = Math::GMPz::Rmpz_init_set_ui($fermat // 0);    # Fermat base

    my @omega_primes;
    my %seen;                                               # dedup guard (used only when $fermat is set)
    my %znorder;                                            # memoised multiplicative orders: p -> ord_{p}(fermat)

    # Hoist dispatch outside the closure.
    my $next_prime =
      HAS_PRIME_UTIL
      ? \&Math::Prime::Util::next_prime
      : \&Math::Prime::Util::GMP::next_prime;

    my $znorder_fn =
      HAS_PRIME_UTIL
      ? \&Math::Prime::Util::znorder
      : \&Math::Prime::Util::GMP::znorder;

    my $is_prime_power_fn =
      HAS_PRIME_UTIL
      ? \&Math::Prime::Util::is_prime_power
      : \&Math::Prime::Util::GMP::is_prime_power;

    my $gcd_fn =
      HAS_PRIME_UTIL
      ? \&Math::Prime::Util::gcd
      : \&Math::Prime::Util::GMP::gcd;

    my $powmod_fn =
      HAS_PRIME_UTIL
      ? \&Math::Prime::Util::powmod
      : \&Math::Prime::Util::GMP::powmod;

    my $generator;
    $generator = sub {
        my ($m, $lo, $j, %args) = @_;

        # hi = floor( floor(B / m)^(1/j) )
        Math::GMPz::Rmpz_tdiv_q($su, $B, $m);
        Math::GMPz::Rmpz_root($su, $su, $j);
        Math::GMPz::Rmpz_fits_ulong_p($su) || die "Too large value!";
        my $hi = Math::GMPz::Rmpz_get_ui($su);

        return if $lo > $hi;

        # ------------------------------------------------------------------
        # Base case j == 1: choose the final prime factor.
        # ------------------------------------------------------------------
        if ($j == 1) {
            my $L = $args{lambda};

            # Fast path: no Fermat filter, or lambda == 1.
            if (!$fermat or Math::GMPz::Rmpz_cmp_ui($L, 1) == 0) {

                for (my $p = $next_prime->($lo - 1) ; $p <= $hi ; $p = $next_prime->($p)) {
                    next if ($fermat and $fermat % $p == 0);
                    next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

                    # Starting value: p^2 when (fermat && m==1), else m*p.
                    if ($fermat and Math::GMPz::Rmpz_cmp_ui($m, 1) == 0) {
                        Math::GMPz::Rmpz_set_ui($sv, $p);
                    }
                    else {
                        Math::GMPz::Rmpz_set($sv, $m);
                    }

                    for (Math::GMPz::Rmpz_mul_ui($sv, $sv, $p) ; Math::GMPz::Rmpz_cmp($sv, $B) <= 0 ; Math::GMPz::Rmpz_mul_ui($sv, $sv, $p)) {
                        next unless Math::GMPz::Rmpz_cmp($sv, $A) >= 0;

                        if ($fermat) {

                            # Check: base^(v-1) ≡ 1 (mod v)
                            Math::GMPz::Rmpz_sub_ui($su, $sv, 1);
                            Math::GMPz::Rmpz_powm($su, $sw, $su, $sv);
                            last unless Math::GMPz::Rmpz_cmp_ui($su, 1) == 0;
                        }

                        my $val =
                            Math::GMPz::Rmpz_fits_ulong_p($sv)
                          ? Math::GMPz::Rmpz_get_ui($sv)
                          : Math::GMPz::Rmpz_init_set($sv);

                        push @omega_primes, $val
                          unless $fermat && $seen{$val}++;
                    }
                }
                return;
            }

            # Fermat path with lambda > 1: candidates satisfy
            # p ≡ m^{-1} (mod lambda).
            Math::GMPz::Rmpz_invert($sv, $m, $L);
            return if Math::GMPz::Rmpz_cmp_ui($sv, $hi) > 0;

            Math::GMPz::Rmpz_fits_ulong_p($L)  || die "Too large value!";
            Math::GMPz::Rmpz_fits_ulong_p($sv) || die "Too large value!";
            my $step = Math::GMPz::Rmpz_get_ui($L);
            my $t    = Math::GMPz::Rmpz_get_ui($sv);
            return if $t > $hi;
            $t += $step while $t < $lo;
            return if $t > $hi;

            for (my $p = $t ; $p <= $hi ; $p += $step) {
                next
                  unless $is_prime_power_fn->($p)
                  && Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $p) == 1
                  && $gcd_fn->($fermat, $p) == 1;
                next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

                Math::GMPz::Rmpz_mul_ui($sv, $m, $p);

                if ($k == 1 and _is_prob_prime($p) and Math::GMPz::Rmpz_cmp_ui($m, 1) == 0) {

                    # Single-prime case handled by outer k==1 logic; skip.
                }
                elsif (Math::GMPz::Rmpz_cmp($sv, $A) >= 0) {
                    Math::GMPz::Rmpz_sub_ui($su, $sv, 1);
                    my $z = ($znorder{$p} //= $znorder_fn->($fermat, $p));
                    if (Math::GMPz::Rmpz_divisible_ui_p($su, $z)) {
                        my $val =
                            Math::GMPz::Rmpz_fits_ulong_p($sv)
                          ? Math::GMPz::Rmpz_get_ui($sv)
                          : Math::GMPz::Rmpz_init_set($sv);
                        push @omega_primes, $val unless $seen{$val}++;
                    }
                }
            }
            return;
        }

        # ------------------------------------------------------------------
        # Recursive case j > 1: choose one prime factor and recurse.
        # Per-frame GMPz objects prevent aliasing across recursive calls.
        # ------------------------------------------------------------------
        my $lambda = $args{lambda};
        my $lv     = Math::GMPz::Rmpz_init();    # m * p^e  (loop variable)
        my ($lu, $lcm);
        if ($fermat) {
            $lu  = Math::GMPz::Rmpz_init();      # p^e  (for Fermat power check)
            $lcm = Math::GMPz::Rmpz_init();
        }

        for (my ($p, $r) = ($lo) ; $p <= $hi ; $p = $r) {
            $r = $next_prime->($p);
            next if ($fermat and $fermat % $p == 0);
            next if ($strong and !_strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr}));

            my $z;
            if ($fermat) {
                Math::GMPz::Rmpz_set_ui($lu, $p);
                $z = ($znorder{$p} //= $znorder_fn->($fermat, $p));
                next unless Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $z) == 1;
                Math::GMPz::Rmpz_lcm_ui($lcm, $lambda, $z);
            }

            for (Math::GMPz::Rmpz_mul_ui($lv, $m, $p) ; Math::GMPz::Rmpz_cmp($lv, $B) <= 0 ; Math::GMPz::Rmpz_mul_ui($lv, $lv, $p)) {
                $generator->($lv, $r, $j - 1, %args, (defined $lcm ? (lambda => $lcm) : ()),);

                if ($fermat) {
                    Math::GMPz::Rmpz_mul_ui($lu, $lu, $p);
                    last unless $powmod_fn->($fermat, $z, $lu) == 1;
                }
            }
        }
    };

    # ------------------------------------------------------------------
    # Kick off the generator.
    # ------------------------------------------------------------------
    if ($strong) {
        $generator->(
                     Math::GMPz::Rmpz_init_set_ui(1), 2, $k,
                     lambda => Math::GMPz::Rmpz_init_set_ui(1),
                     k_exp  => 0,
                     congr  => 1,
                    );
        for my $v (0 .. Math::Prime::Util::GMP::logint($B, 2)) {
            $generator->(
                         Math::GMPz::Rmpz_init_set_ui(1), 2, $k,
                         lambda => Math::GMPz::Rmpz_init_set_ui(1),
                         k_exp  => $v,
                         congr  => -1,
                        );
        }
    }
    else {
        $generator->(Math::GMPz::Rmpz_init_set_ui(1), 2, $k, ($fermat ? (lambda => Math::GMPz::Rmpz_init_set_ui(1)) : ()),);
    }

    undef %znorder;
    undef $generator;    # break circular reference

    @omega_primes = sort { $a <=> $b } @omega_primes;
    return \@omega_primes;
}

# =============================================================================
# Main entry point: sieve for k-omega primes in the interval [$from, $to].
#
# Strategy selection:
#   (a) Trivial base cases.
#   (b) Math::Prime::Util::prime_powers  — k==1, no Fermat, ulong range.
#   (c) _sieve_small_range               — |B-A| < B^(1/(k+1)).
#   (d) Math::Prime::Util::omega_primes  — no Fermat, ulong range.
#   (e) _sieve_omega_ulong               — Fermat, ulong range.
#   (f) _sieve_omega_gmp                 — any, bignum range.
# =============================================================================
sub _sieve_omega_primes {
    my ($from, $to, $k, %opt) = @_;

    my $fermat = $opt{fermat};
    my $strong = $opt{strong};

    # ------------------------------------------------------------------
    # 1. Base cases
    # ------------------------------------------------------------------
    return []  if $fermat and $k < 1;
    return [1] if $k == 0 and $to >= 1 and $from <= 1;
    return []  if $k == 0;

    # Computed once; used by both the fast path and the dispatch below.
    my $fits_ulong = Math::GMPz::Rmpz_fits_ulong_p($to);

    # ------------------------------------------------------------------
    # 2. Fast path: prime powers via Math::Prime::Util
    # ------------------------------------------------------------------
    if (HAS_PRIME_UTIL and $k == 1 and !$fermat and $fits_ulong) {
        return scalar Math::Prime::Util::prime_powers(Math::GMPz::Rmpz_get_ui($from), Math::GMPz::Rmpz_get_ui($to),);
    }

    # ------------------------------------------------------------------
    # 3. Small-range optimisation: brute-force when |B-A| < B^(1/(k+1))
    # ------------------------------------------------------------------
    {
        state $dt = Math::GMPz::Rmpz_init_nobless();
        state $du = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_sub($dt, $to, $from);
        Math::GMPz::Rmpz_root($du, $to, $k + 1);
        if (Math::GMPz::Rmpz_cmp($dt, $du) < 0) {
            $VERBOSE && say STDERR "omega_primes: small range: ($from, $to) with k = $k";
            return _sieve_small_range($from, $to, $k, $fermat, $strong);
        }
    }

    # ------------------------------------------------------------------
    # 4. Dispatch to the appropriate sieve
    # ------------------------------------------------------------------
    if (HAS_PRIME_UTIL and $fits_ulong) {
        unless ($fermat) {

            # Direct library call for the common case.
            return Math::Prime::Util::omega_primes($k, Math::GMPz::Rmpz_get_ui($from), Math::GMPz::Rmpz_get_ui($to),);
        }
        return _sieve_omega_ulong($from, $to, $k, $fermat, $strong);
    }

    return _sieve_omega_gmp($from, $to, $k, $fermat, $strong);
}

sub omega_prime_divisors {
    my ($n, $k) = @_;

    if (!defined($k)) {
        my @list;

        foreach my $k (0 .. $n->omega) {
            push @list, $n->omega_prime_divisors(_set_int($k));
        }

        return _array(\@list);
    }

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $k = _any2ui($$k) // return _array();
    my $z = _any2mpz($$n) // return _array();

    if ($k == 0) {
        return _array([ONE]);
    }

    if ($k == 1) {
        return $_[0]->prime_power_divisors;
    }

    my @factor_exp = _factor_exp($z);

    if ($k > scalar(@factor_exp)) {
        return _array();
    }

    my %valuations  = map { @$_ } @factor_exp;
    my @factors     = map { ($_ < ULONG_MAX) ? $_ : Math::GMPz::Rmpz_init_set_str("$_", 10) } map { $_->[0] } @factor_exp;
    my $factors_end = $#factors;

    my $t = Math::GMPz::Rmpz_init();

    my @list;

    sub {
        my ($m, $k, $i) = @_;

        Math::GMPz::Rmpz_div($t, $z, $m);
        Math::GMPz::Rmpz_root($t, $t, $k);

        my $L =
            Math::GMPz::Rmpz_fits_ulong_p($t)
          ? Math::GMPz::Rmpz_get_ui($t)
          : Math::GMPz::Rmpz_init_set($t);

        foreach my $j ($i .. $factors_end) {

            my $q = $factors[$j];

            if (($k > 1 and $j == $factors_end) or ($q > $L)) {
                last;
            }

            my $v = $m * $q;

            foreach (1 .. $valuations{$q}) {

                if ($k == 1) {
                    push @list, $v;
                }
                else {
                    __SUB__->($v, $k - 1, $j + 1);
                }

                $v *= $q;
            }
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), $k, 0);

    _array([map { bless \$_ } sort { $a <=> $b } @list]);
}

sub omega_primes {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @omega_primes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_omega_primes($from, $to, $k)};
#>>>

    _array(\@omega_primes);
}

sub omega_inverse {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->omega_primes($from, $to);
}

sub fermat_psp {
    my ($k, $base, $from, $to) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $base = _any2ui($$base) // return _array();
    $k    = _any2ui($$k)    // return _array();

    if ($base <= 1) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @fermat_pseudoprimes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_omega_primes($from, $to, $k, fermat => $base)};
#>>>

    _array(\@fermat_pseudoprimes);
}

sub strong_fermat_psp {
    my ($k, $base, $from, $to) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $base = _any2ui($$base) // return _array();
    $k    = _any2ui($$k)    // return _array();

    if ($base <= 1) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @fermat_pseudoprimes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_omega_primes($from, $to, $k, fermat => $base, strong => 1)};
#>>>

    _array(\@fermat_pseudoprimes);
}

sub prime_powers {
    (ONE)->omega_primes(@_);
}

sub omega_primes_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    my $step = ($k > 8) ? Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10) : 1e7;

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_omega_primes($_[0], $_[1], $k) });
}

*each_omega_prime = \&omega_primes_each;

sub prime_powers_each {
    (ONE)->omega_primes_each(@_);
}

*each_prime_power = \&prime_powers_each;

# =============================================================================
# Note: _strong_fermat_check is defined in _sieve_omega_primes's companion
# refactoring and is shared across both sieves.  Signature:
#   _strong_fermat_check($p, $base, $k_exp, $congr)
# Returns true iff v2(p-1) > k_exp  AND
#   base^((p-1) >> (v2(p-1)-k_exp)) ≡ congr (mod p).
# =============================================================================

# =============================================================================
# Brute-force sieve for k-almost primes in [$from, $to].
# Used when the interval is small enough that iterating every integer is faster
# than the recursive generator: |B - A| < B^(1/k).
# =============================================================================
sub _almost_prime_small_range {
    my ($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree) = @_;

    my $v        = Math::GMPz::Rmpz_init_set($from);
    my $o        = bless \$v;                          # Sidef::Types::Number::Number
    my $base_obj = bless \$fermat;
    my $k_obj    = bless \$k;

    my @arr;
    for (; Math::GMPz::Rmpz_cmp($v, $to) <= 0 ; Math::GMPz::Rmpz_add_ui($v, $v, 1)) {

        if ($fermat) {
            next unless $strong
              ? $o->is_strong_fermat_psp($base_obj)
              : $o->is_fermat_psp($base_obj);
        }
        next unless !$carmichael       || $o->is_carmichael;
        next unless !$lucas_carmichael || $o->is_lucas_carmichael;

        if ($squarefree) {
            if    ($k == 1) { next unless $o->is_prime }
            elsif ($k == 2) { next unless $o->is_squarefree_semiprime }
            else            { next unless $o->is_squarefree_almost_prime($k_obj) }
        }
        else {
            if    ($k == 1) { next unless $o->is_prime }
            elsif ($k == 2) { next unless $o->is_semiprime }
            else            { next unless $o->is_almost_prime($k_obj) }
        }
        push @arr, Math::GMPz::Rmpz_init_set($v);
    }
    return \@arr;
}

# =============================================================================
# Sieve for k-almost primes using native unsigned-long arithmetic.
# Pre-conditions: HAS_PRIME_UTIL is true AND $to fits in an unsigned long.
# =============================================================================
sub _almost_prime_ulong {
    my ($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree) = @_;

    my $A = Math::GMPz::Rmpz_get_ui($from);
    my $B = Math::GMPz::Rmpz_get_ui($to);

    # Compute the effective lower bound A and the optional ceiling on the
    # largest prime factor (max_p), depending on which filter is active.
    my $max_p = undef;

    if ($squarefree) {
        if ($carmichael or $lucas_carmichael) {
            $A = Math::Prime::Util::vecmax($A, Math::Prime::Util::GMP::divint(Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k + 1), 10), 2),);
            if ($carmichael) {

                # For n = p1*…*pk, the largest factor satisfies
                # p_k ≤ (1 + sqrt(8n+1)) / 4.
                $max_p = (1 + Math::Prime::Util::GMP::sqrtint(Math::Prime::Util::GMP::addint(Math::Prime::Util::GMP::mulint(8, $B), 1))) >> 2;
            }
            elsif ($lucas_carmichael) {
                $max_p = Math::Prime::Util::sqrtint($B);
            }
        }
        else {
            $A = Math::Prime::Util::vecmax($A, Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10),);
        }
    }
    else {
        # Smallest k-almost prime is 2^k.
        $A = Math::Prime::Util::vecmax($A, Math::Prime::Util::GMP::powint(2, $k));
    }

    return [] if $A > $B;

    my @almost_primes;
    my %znorder;    # memoised multiplicative orders: p -> ord_p(fermat)

    my $generator;
    $generator = sub {
        my ($m, $p, $k, %args) = @_;
        my $lambda = $args{lambda};

        # ------------------------------------------------------------------
        # Base case k == 1: choose the final prime factor.
        # ------------------------------------------------------------------
        if ($k == 1) {
            my $lo = $args{from};
            my $hi = $args{upto};

            # Carmichael constraint: the largest prime factor p_k must be ≤ m
            # (the product of all smaller factors), otherwise (p_k - 1) cannot
            # divide (n - 1).
            $hi = $m     if ($carmichael     && $m < $hi);
            $hi = $max_p if (defined($max_p) && $max_p < $hi);

            return if $lo > $hi;

            if ($carmichael) {

                # Valid primes satisfy p ≡ m^{-1} (mod lambda) so that
                # (m*p - 1) ≡ 0 (mod (p - 1)).
                my $L = $args{lambda};
                my $t = Math::Prime::Util::invmod($m, $L);
                return                                              if $t > $hi;
                $t += Math::Prime::Util::cdivint($lo - $t, $L) * $L if $t < $lo;
                return                                              if $t > $hi;

                for (my $p = $t ; $p <= $hi ; $p += $L) {
                    my $n = $m * $p;
                    if (($n - 1) % ($p - 1) == 0 && Math::Prime::Util::is_prime($p)) {
                        if ($strong) {
                            next unless _strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr});
                        }
                        push @almost_primes, $n;
                    }
                }
            }
            elsif ($lucas_carmichael) {

                # Valid primes satisfy p ≡ -m^{-1} (mod lambda) so that
                # (m*p + 1) ≡ 0 (mod (p + 1)).
                my $L = $args{lambda};
                my $t = $L - Math::Prime::Util::invmod($m, $L);
                return                                              if $t > $hi;
                $t += Math::Prime::Util::cdivint($lo - $t, $L) * $L if $t < $lo;
                return                                              if $t > $hi;

                for (my $p = $t ; $p <= $hi ; $p += $L) {
                    my $n = $m * $p;
                    push(@almost_primes, $n)
                      if (($n + 1) % ($p + 1) == 0 && Math::Prime::Util::is_prime($p));
                }
            }
            elsif ($fermat) {

                # Valid primes satisfy p ≡ m^{-1} (mod lambda) so that
                # ord_p(base) | (m*p - 1).
                my $L = $args{lambda};
                my $t = Math::Prime::Util::invmod($m, $L);
                return                                              if $t > $hi;
                $t += Math::Prime::Util::cdivint($lo - $t, $L) * $L if $t < $lo;
                return                                              if $t > $hi;

                for (my $p = $t ; $p <= $hi ; $p += $L) {
                    next unless Math::Prime::Util::is_prime($p);
                    next if $fermat % $p == 0;

                    my $n = $m * $p;
                    if (($n - 1) % ($znorder{$p} //= Math::Prime::Util::znorder($fermat, $p)) == 0) {
                        if ($strong) {
                            next unless _strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr});
                        }
                        push @almost_primes, $n;
                    }
                }
            }
            else {
                # Plain almost primes: any prime in [lo, hi].
                Math::Prime::Util::forprimes(sub { push @almost_primes, $m * $_ }, $lo, $hi);
            }

            return;
        }

        # ------------------------------------------------------------------
        # Recursive case k > 1: choose one prime factor and recurse.
        # ------------------------------------------------------------------
        my $hi = Math::Prime::Util::rootint(Math::Prime::Util::divint($B, $m), $k);
        my $L;

        for (my $r ; $p <= $hi ; $p = $r) {
            $r = Math::Prime::Util::next_prime($p);

            # Strong Fermat filter on each chosen intermediate prime.
            if ($strong && $fermat) {
                next if $fermat % $p == 0;
                next unless _strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr});
            }

            # Accumulate lambda (LCM of group orders seen so far) so the
            # final prime can be found by an arithmetic-progression sieve.
            if ($carmichael) {
                next unless Math::Prime::Util::gcd($m, $p >> 1) == 1;
                $L = Math::Prime::Util::lcm($lambda, $p - 1);
            }
            elsif ($lucas_carmichael) {
                next unless Math::Prime::Util::gcd($m, ($p >> 1) + 1) == 1;
                $L = Math::Prime::Util::lcm($lambda, $p + 1);
            }
            elsif ($fermat) {
                next if $fermat % $p == 0;
                my $z = ($znorder{$p} //= Math::Prime::Util::znorder($fermat, $p));
                next unless Math::Prime::Util::gcd($m, $z) == 1;
                $L = Math::Prime::Util::lcm($lambda, $z);
            }

            my $t = $m * $p;
            my $u = Math::Prime::Util::divint($A, $t);
            my $v = Math::Prime::Util::divint($B, $t);
            ++$u if $t * $u < $A;

            unless ($u > $v) {
                $p = $r if $squarefree;          # enforce strictly increasing primes
                $u = $p if $k == 2 && $p > $u;
                $generator->($t, $p, $k - 1, %args, ($k == 2 ? (from => $u, upto => $v) : ()), (defined($L) ? (lambda => $L) : ()),);
            }
        }
    };

    # ------------------------------------------------------------------
    # Kick off the generator.
    # ------------------------------------------------------------------
    if ($strong) {

        # Case where base^d ≡ 1 (mod p), d = odd part of p-1.
        $generator->(1, 2, $k, lambda => 1, k_exp => 0, congr => 1);

        # Cases where base^(d·2^v) ≡ -1 (mod p), for v = 0 .. floor(log2 B).
        for my $v (0 .. Math::Prime::Util::logint($B, 2)) {
            $generator->(1, 2, $k, lambda => 1, k_exp => $v, congr => -1);
        }
    }
    else {
        $generator->(1, ($carmichael || $lucas_carmichael) ? 3 : 2, $k, lambda => 1,);
    }

    undef %znorder;
    undef $generator;    # break circular reference

    @almost_primes = sort { $a <=> $b } @almost_primes;
    return \@almost_primes;
}

# =============================================================================
# Sieve for k-almost primes using GMP big-integer arithmetic.
# Used when $to does not fit in an unsigned long.
#
# Allocation strategy
# -------------------
# $su / $sv: shared scratch captured by the closure.  Safe because:
#   * The k==1 base case never recurses.
#   * The k>1 recursive case extracts plain-scalar values from $su/$sv with
#     Rmpz_get_ui / Rmpz_get_str before each recursive call, so recursive
#     frames cannot alias the caller's live values.
# $x / $L:  allocated fresh per k>1 frame; $L is passed to the callee by
#            reference (no copy) — safe because the callee returns before the
#            caller overwrites $L for the next prime.
# =============================================================================
sub _almost_prime_gmp {
    my ($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree) = @_;

    my $A = Math::GMPz::Rmpz_init_set($from);
    my $B = Math::GMPz::Rmpz_init_set($to);

    # Closure-captured scratch variables (see allocation strategy above).
    my $su = Math::GMPz::Rmpz_init();
    my $sv = Math::GMPz::Rmpz_init();

    my $max_p = undef;

    if ($squarefree) {
        if ($carmichael or $lucas_carmichael) {
            Math::GMPz::Rmpz_set_str($su, Math::Prime::Util::GMP::divint(Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k + 1), 10), 2), 10,);

            if ($carmichael) {

                # max_p = floor((1 + sqrt(8*B + 1)) / 4)
                $max_p = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_mul_2exp($max_p, $B, 3);
                Math::GMPz::Rmpz_add_ui($max_p, $max_p, 1);
                Math::GMPz::Rmpz_sqrt($max_p, $max_p);
                Math::GMPz::Rmpz_add_ui($max_p, $max_p, 1);
                Math::GMPz::Rmpz_div_2exp($max_p, $max_p, 2);
                $max_p = Math::GMPz::Rmpz_get_ui($max_p)
                  if Math::GMPz::Rmpz_fits_ulong_p($max_p);
            }
            elsif ($lucas_carmichael) {
                $max_p = Math::GMPz::Rmpz_init();
                Math::GMPz::Rmpz_sqrt($max_p, $B);
                $max_p = Math::GMPz::Rmpz_get_ui($max_p)
                  if Math::GMPz::Rmpz_fits_ulong_p($max_p);
            }
        }
        else {
            Math::GMPz::Rmpz_set($su, _cached_pn_primorial($k));
        }
    }
    else {
        # su = 2^k  (smallest k-almost prime)
        Math::GMPz::Rmpz_set_ui($su, 0);
        Math::GMPz::Rmpz_setbit($su, $k);
    }

    # A = max(A, su)
    Math::GMPz::Rmpz_set($A, $su) if Math::GMPz::Rmpz_cmp($su, $A) > 0;
    return []                     if Math::GMPz::Rmpz_cmp($A,  $B) > 0;

    my @almost_primes;
    my %znorder;    # memoised multiplicative orders: p -> ord_p(fermat)

    # Hoist dispatch outside the closure.
    my $next_prime  = HAS_PRIME_UTIL ? \&Math::Prime::Util::next_prime : \&Math::Prime::Util::GMP::next_prime;
    my $is_prime_fn = HAS_PRIME_UTIL ? \&Math::Prime::Util::is_prime   : \&Math::Prime::Util::GMP::is_prime;
    my $znorder_fn  = HAS_PRIME_UTIL ? \&Math::Prime::Util::znorder    : \&Math::Prime::Util::GMP::znorder;
    my $cdivint     = HAS_PRIME_UTIL ? \&Math::Prime::Util::cdivint    : \&Math::Prime::Util::GMP::cdivint;

    my $generator;
    $generator = sub {
        my ($m, $lo, $k, %args) = @_;
        my $lambda = $args{lambda};

        # ------------------------------------------------------------------
        # Base case k == 1: choose the final prime factor.
        # ------------------------------------------------------------------
        if ($k == 1) {

            if ($carmichael || $fermat || $lucas_carmichael) {

                my $from_p = $args{from};
                my $to_p   = $args{upto};

                # Carmichael: the largest prime factor must not exceed m.
                $to_p = Math::GMPz::Rmpz_get_ui($m)
                  if ($carmichael && Math::GMPz::Rmpz_cmp_ui($m, $to_p) < 0);

                $to_p = $max_p if defined($max_p) && $max_p < $to_p;

                return if $from_p > $to_p;

                # Starting point of the arithmetic progression.
                # Carmichael / Fermat: p ≡  m^{-1}  (mod lambda)
                # Lucas-Carmichael:    p ≡ -m^{-1}  (mod lambda)
                Math::GMPz::Rmpz_invert($sv, $m, $lambda);
                Math::GMPz::Rmpz_sub($sv, $lambda, $sv) if $lucas_carmichael;

                return if Math::GMPz::Rmpz_cmp_ui($sv, $to_p) > 0;

                Math::GMPz::Rmpz_fits_ulong_p($lambda) || die "Too large value!";
                Math::GMPz::Rmpz_fits_ulong_p($sv)     || die "Too large value!";

                my $step = Math::GMPz::Rmpz_get_ui($lambda);
                my $t    = Math::GMPz::Rmpz_get_ui($sv);
                $t += $cdivint->($from_p - $t, $step) * $step if $t < $from_p;
                return                                        if $t > $to_p;

                for (my $p = $t ; $p <= $to_p ; $p += $step) {
                    next unless $is_prime_fn->($p);
                    next if ($fermat && $fermat % $p == 0);

                    if ($strong && $fermat) {
                        next unless _strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr});
                    }

                    # Compute n = m * p and n ± 1 for the divisibility test.
                    Math::GMPz::Rmpz_mul_ui($sv, $m, $p);
                    $lucas_carmichael
                      ? Math::GMPz::Rmpz_add_ui($su, $sv, 1)
                      : Math::GMPz::Rmpz_sub_ui($su, $sv, 1);

                    my $ok =
                        $lucas_carmichael ? Math::GMPz::Rmpz_divisible_ui_p($su, $p + 1)
                      : $carmichael       ? Math::GMPz::Rmpz_divisible_ui_p($su, $p - 1)
                      : do {
                        my $ord = ($znorder{$p} //= $znorder_fn->($fermat, $p));
                        Math::GMPz::Rmpz_divisible_ui_p($su, $ord);
                      };

                    if ($ok) {
                        push @almost_primes, Math::GMPz::Rmpz_fits_ulong_p($sv)
                          ? Math::GMPz::Rmpz_get_ui($sv)
                          : Math::GMPz::Rmpz_init_set($sv);
                    }
                }
            }
            else {
                # Plain almost primes: any prime q in [from, upto], push m*q.
                for my $q (@{_primes($args{from}, $args{upto})}) {
                    Math::GMPz::Rmpz_mul_ui($su, $m, $q);
                    push @almost_primes, Math::GMPz::Rmpz_fits_ulong_p($su)
                      ? Math::GMPz::Rmpz_get_ui($su)
                      : Math::GMPz::Rmpz_init_set($su);
                }
            }

            return;
        }

        # ------------------------------------------------------------------
        # Recursive case k > 1: choose one prime factor and recurse.
        # $su is repurposed here for rootint scratch (safe: written before
        # each read, and no interleaving with k==1 branch).
        # ------------------------------------------------------------------
        Math::GMPz::Rmpz_tdiv_q($su, $B, $m);
        Math::GMPz::Rmpz_root($su, $su, $k);

        my $hi =
            Math::GMPz::Rmpz_fits_ulong_p($su)
          ? Math::GMPz::Rmpz_get_ui($su)
          : Math::GMPz::Rmpz_get_str($su, 10);

        return if $lo > $hi;

        # Per-frame allocations prevent aliasing across recursive calls.
        my $x = Math::GMPz::Rmpz_init();
        my $L =
          ($carmichael || $lucas_carmichael || $fermat)
          ? Math::GMPz::Rmpz_init()
          : undef;

        for (my ($p, $r) = ($lo) ; $p <= $hi ; $p = $r) {
            $r = $next_prime->($p);

            if ($strong && $fermat) {
                next if $fermat % $p == 0;
                next unless _strong_fermat_check($p, $fermat, $args{k_exp}, $args{congr});
            }

            if ($carmichael) {
                next unless Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $p >> 1) == 1;
                Math::GMPz::Rmpz_lcm_ui($L, $lambda, $p - 1);
            }
            elsif ($lucas_carmichael) {
                next unless Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, ($p >> 1) + 1) == 1;
                Math::GMPz::Rmpz_lcm_ui($L, $lambda, $p + 1);
            }
            elsif ($fermat) {
                next if $fermat % $p == 0;
                my $ord = ($znorder{$p} //= $znorder_fn->($fermat, $p));
                next unless Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $ord) == 1;
                Math::GMPz::Rmpz_lcm_ui($L, $lambda, $ord);
            }

            # x = m * p; su = ceil(A/x); sv = floor(B/x).
            Math::GMPz::Rmpz_mul_ui($x, $m, $p);
            Math::GMPz::Rmpz_cdiv_q($su, $A, $x);
            Math::GMPz::Rmpz_div($sv, $B, $x);

            if (Math::GMPz::Rmpz_cmp($su, $sv) <= 0) {

                $p = $r if $squarefree;    # enforce strictly increasing primes

                # For k==2, raise the lower bound for the next prime to at
                # least p (prevents double-counting pairs).
                if ($k == 2 && Math::GMPz::Rmpz_cmp_ui($su, $p) < 0) {
                    Math::GMPz::Rmpz_set_ui($su, $p);
                }

                # Extract plain scalars before the recursive call so that
                # $su/$sv can be safely overwritten inside the callee.
                $generator->(
                             $x, $p,
                             $k - 1,
                             %args,
                             (
                              $k == 2
                              ? (
                                 from => (
                                            Math::GMPz::Rmpz_fits_ulong_p($su)
                                          ? Math::GMPz::Rmpz_get_ui($su)
                                          : Math::GMPz::Rmpz_get_str($su, 10)
                                         ),
                                 upto => (
                                            Math::GMPz::Rmpz_fits_ulong_p($sv)
                                          ? Math::GMPz::Rmpz_get_ui($sv)
                                          : Math::GMPz::Rmpz_get_str($sv, 10)
                                         ),
                                )
                              : ()
                             ),
                             (defined($L) ? (lambda => $L) : ()),
                            );
            }
        }
    };

    # ------------------------------------------------------------------
    # Kick off the generator.
    # ------------------------------------------------------------------
    if ($strong && $fermat) {

        # Case where base^d ≡ 1 (mod p), d = odd part of p-1.
        $generator->(
                     Math::GMPz::Rmpz_init_set_ui(1), 2, $k,
                     lambda => Math::GMPz::Rmpz_init_set_ui(1),
                     k_exp  => 0,
                     congr  => 1,
                    );

        # Cases where base^(d·2^v) ≡ -1 (mod p), for v = 0 .. floor(log2 B).
        for my $v (0 .. Math::Prime::Util::GMP::logint($B, 2)) {
            $generator->(
                         Math::GMPz::Rmpz_init_set_ui(1), 2, $k,
                         lambda => Math::GMPz::Rmpz_init_set_ui(1),
                         k_exp  => $v,
                         congr  => -1,
                        );
        }
    }
    else {
        $generator->(
                     Math::GMPz::Rmpz_init_set_ui(1),
                     ($carmichael || $lucas_carmichael) ? 3 : 2,
                     $k,
                     (
                        ($carmichael || $lucas_carmichael || $fermat)
                      ? (lambda => Math::GMPz::Rmpz_init_set_ui(1))
                      : ()
                     ),
                    );
    }

    undef %znorder;
    undef $generator;    # break circular reference

    @almost_primes = sort { $a <=> $b } @almost_primes;
    return \@almost_primes;
}

# =============================================================================
# Main entry point: sieve for k-almost primes in the interval [$from, $to].
#
# Strategy selection:
#   (a) Trivial base cases.
#   (b) Primes (k==1) — direct library sieve.
#   (c) _almost_prime_small_range  — |B-A| < B^(1/k).
#   (d) Math::Prime::Util::almost_primes — no filters, ulong range.
#   (e) Math::Prime::Util::semi_primes  — k==2, no Fermat, ulong range.
#   (f) _almost_prime_ulong  — general, ulong range.
#   (g) _almost_prime_gmp    — general, bignum range.
# =============================================================================
sub _sieve_almost_primes {
    my ($from, $to, $k, %opt) = @_;

    my $strong           = $opt{strong};
    my $fermat           = $opt{fermat};
    my $carmichael       = $opt{carmichael};
    my $lucas_carmichael = $opt{lucas_carmichael};

    # ------------------------------------------------------------------
    # 1. Base cases
    # ------------------------------------------------------------------
    return []  if (($carmichael || $lucas_carmichael) && $k < 3);
    return []  if ($fermat                            && $k < 2);
    return [1] if ($k == 0                            && $to >= 1 && $from <= 1);
    return []  if ($k == 0);

    # ------------------------------------------------------------------
    # 2. k == 1: return primes directly — no almost-prime sieve needed.
    # ------------------------------------------------------------------
    if ($k == 1) {
        return _primes($from, $to);
    }

    my $squarefree = $opt{squarefree};

    # Computed once; referenced in both the small-range check and the dispatch.
    my $fits_ulong = Math::GMPz::Rmpz_fits_ulong_p($to);

    # ------------------------------------------------------------------
    # 3. Small-range optimisation: brute-force when |B-A| < B^(1/k).
    # ------------------------------------------------------------------
    {
        state $dt = Math::GMPz::Rmpz_init_nobless();
        state $du = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_sub($dt, $to, $from);
        Math::GMPz::Rmpz_root($du, $to, $k);
        if (Math::GMPz::Rmpz_cmp($dt, $du) < 0) {
            $VERBOSE and say STDERR "almost_primes: small range: ($from, $to) with k = $k";
            return _almost_prime_small_range($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree,);
        }
    }

    # ------------------------------------------------------------------
    # 4. Library fast paths (ulong range only).
    # ------------------------------------------------------------------
    if (HAS_PRIME_UTIL && $fits_ulong) {

        # Cleanest case: no filter at all — library handles everything.
        if (!$squarefree && !$fermat && !$carmichael && !$lucas_carmichael) {
            return Math::Prime::Util::almost_primes($k, Math::GMPz::Rmpz_get_ui($from), Math::GMPz::Rmpz_get_ui($to),);
        }

        # k==2 without Fermat/Carmichael: semi_primes is fastest.
        # (carmichael/lucas with k==2 already returned [] above.)
        if ($k == 2 && !$fermat && !$carmichael && !$lucas_carmichael) {
            my $A   = Math::GMPz::Rmpz_get_ui($from);
            my $B   = Math::GMPz::Rmpz_get_ui($to);
            my $arr = Math::Prime::Util::semi_primes($A, $B);

            # Squarefree semiprimes = semiprimes that are not perfect squares.
            @$arr = grep { !Math::Prime::Util::is_square($_) } @$arr if $squarefree;
            return $arr;
        }
    }

    # ------------------------------------------------------------------
    # 5. Full recursive sieve.
    # ------------------------------------------------------------------
    if (HAS_PRIME_UTIL && $fits_ulong) {
        return _almost_prime_ulong($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree,);
    }
    return _almost_prime_gmp($from, $to, $k, $fermat, $strong, $carmichael, $lucas_carmichael, $squarefree,);
}

sub almost_prime_divisors {
    my ($n, $k) = @_;

    if (!defined($k)) {
        my @list;

        foreach my $k (0 .. $n->bigomega) {
            push @list, $n->almost_prime_divisors(_set_int($k));
        }

        return _array(\@list);
    }

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $k = _any2ui($$k) // return _array();
    my $z = _any2mpz($$n) // return _array();

    if ($k == 0) {
        return _array([ONE]);
    }

    my @factor_exp  = _factor_exp($z);
    my %valuations  = map { @$_ } @factor_exp;
    my @factors     = map { ($_ < ULONG_MAX) ? $_ : Math::GMPz::Rmpz_init_set_str("$_", 10) } map { $_->[0] } @factor_exp;
    my $factors_end = $#factors;

    if ($k == 1) {
        return _array([map { _set_int($_) } @factors]);
    }

    my $bigomega = 0;

    foreach my $pp (@factor_exp) {
        $bigomega += $pp->[1];
    }

    if ($k > $bigomega) {
        return _array();
    }

    my $t = Math::GMPz::Rmpz_init();

    my @list;

    sub {
        my ($m, $k, $i) = @_;

        Math::GMPz::Rmpz_div($t, $z, $m);

        if ($k == 1) {

            my $L =
                Math::GMPz::Rmpz_fits_ulong_p($t)
              ? Math::GMPz::Rmpz_get_ui($t)
              : Math::GMPz::Rmpz_init_set($t);

            foreach my $j ($i .. $factors_end) {

                my $q = $factors[$j];
                $q > $L and last;

                my $v = ref($q) ? Math::GMPz::Rmpz_remove($t, $m, $q) : do {
                    Math::GMPz::Rmpz_set_ui($t, $q);
                    Math::GMPz::Rmpz_remove($t, $m, $t);
                };

                if ($v < $valuations{$q}) {
                    push @list, $m * $q;
                }
            }

            return;
        }

        Math::GMPz::Rmpz_root($t, $t, $k);

        my $L =
            Math::GMPz::Rmpz_fits_ulong_p($t)
          ? Math::GMPz::Rmpz_get_ui($t)
          : Math::GMPz::Rmpz_init_set($t);

        foreach my $j ($i .. $factors_end) {

            my $q = $factors[$j];
            $q > $L and last;

            my $v = ref($q) ? Math::GMPz::Rmpz_remove($t, $m, $q) : do {
                Math::GMPz::Rmpz_set_ui($t, $q);
                Math::GMPz::Rmpz_remove($t, $m, $t);
            };

            if ($v < $valuations{$q}) {
                __SUB__->($m * $q, $k - 1, $j);
            }
        }
      }
      ->(Math::GMPz::Rmpz_init_set_ui(1), $k, 0);

    _array([map { bless \$_ } sort { $a <=> $b } @list]);
}

sub almost_primes {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @almost_primes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k)};
#>>>

    _array(\@almost_primes);
}

sub bigomega_inverse {
    my ($from, $to, $k) = @_;
    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);
    $k->almost_primes($from, $to);
}

sub _sieve_prime_sig_numbers {
    my ($from, $to, $prime_signature) = @_;

    my $A = Math::GMPz::Rmpz_init_set($from);
    my $B = Math::GMPz::Rmpz_init_set($to);

    my $k = scalar(@$prime_signature);

    # Handle empty prime signature
    if ($k == 0) {
        return ($A <= 1 and 1 <= $B) ? [1] : [];
    }

    my $primorial = _cached_pn_primorial($k);
    if (Math::GMPz::Rmpz_cmp($primorial, $A) > 0) {
        Math::GMPz::Rmpz_set($A, $primorial);
    }

    # Early exit if A is out of bounds
    if (Math::GMPz::Rmpz_cmp($A, $B) > 0) {
        return [];
    }

    my $sum_e = List::Util::sum(@$prime_signature) || return [];

    if ($sum_e >= Math::GMPz::Rmpz_sizeinbase($B, 2)) {
        return [];
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    my @results;

    sub {
        my ($m, $lo, $rem_sig, $rem_sum) = @_;

        Math::GMPz::Rmpz_tdiv_q($t, $B, $m);
        Math::GMPz::Rmpz_root($t, $t, $rem_sum) if ($rem_sum > 1);

        if (Math::GMPz::Rmpz_cmp_ui($t, $lo) < 0) {
            return;
        }

        Math::GMPz::Rmpz_fits_ulong_p($t) || die "Too large value!";

        my $k  = scalar(@$rem_sig);
        my $hi = Math::GMPz::Rmpz_get_ui($t);

        my %seen;
        foreach my $i (0 .. $k - 1) {
            my $e = $rem_sig->[$i];

            next if $seen{$e}++;

            my @new_sig = @$rem_sig;
            splice(@new_sig, $i, 1);

            # Base case for recursion
            if ($k == 1) {

                Math::GMPz::Rmpz_cdiv_q($t, $A, $m);
                my $exact_root = Math::GMPz::Rmpz_root($t, $t, $e);
                Math::GMPz::Rmpz_add_ui($t, $t, 1) if !$exact_root;

                if (Math::GMPz::Rmpz_cmp_ui($t, $lo) > 0) {
                    $lo = Math::GMPz::Rmpz_get_ui($t);
                }

                foreach my $p (@{_primes($lo, $hi)}) {

                    if ($e == 1) {
                        Math::GMPz::Rmpz_mul_ui($t, $m, $p);
                    }
                    else {
                        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e);
                        Math::GMPz::Rmpz_mul($t, $m, $t);
                    }
                    push @results,
                      (
                          Math::GMPz::Rmpz_fits_ulong_p($t)
                        ? Math::GMPz::Rmpz_get_ui($t)
                        : Math::GMPz::Rmpz_init_set($t)
                      );
                }

                return;
            }
            else {

                my $u       = Math::GMPz::Rmpz_init();
                my $new_sum = $rem_sum - $e;

                for (my $p = $lo ; $p <= $hi ;) {

                    if ($e == 1) {
                        Math::GMPz::Rmpz_mul_ui($u, $m, $p);
                    }
                    else {
                        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e);
                        Math::GMPz::Rmpz_mul($u, $m, $t);
                    }

                    my $r = HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p);
                    __SUB__->($u, $r, \@new_sig, $new_sum);
                    $p = $r;
                }
            }
        }
      }
      ->(
        do {
            state $m = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_set_ui($m, 1);
            $m;
        },
        2,
        $prime_signature,
        $sum_e
      );

    return \@results;
}

sub prime_signature_numbers {
    my ($from, $to, $signature) = @_;

    Scalar::Util::reftype($signature) eq 'ARRAY'
      or die "usage: prime_signature_numbers(A, B, [...])";

    my @sig = map { my $v = "$_"; $v <= 0 and die "invalid prime signature (entry: $v)"; $v } @$signature;

    ref($to) eq __PACKAGE__ or _valid(\$to);

    $from = _any2mpz($$from) // return _array();
    $to   = _any2mpz($$to)   // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

    my @numbers = map { bless \$_ } sort { $a <=> $b } @{_sieve_prime_sig_numbers($from, $to, \@sig)};
    _array(\@numbers);
}

*prime_signature_inverse = \&prime_signature_numbers;
*inverse_prime_signature = \&prime_signature_numbers;

sub _prime_sig_count {
    my ($n, $prime_signature) = @_;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return 0;

    my $k = scalar(@$prime_signature);

    if ($k == 0) {
        return 1;
    }

    if (Math::GMPz::Rmpz_sizeinbase($n, 2) <= $k) {
        return 0;
    }

    if (Math::GMPz::Rmpz_cmp($n, _cached_pn_primorial($k)) < 0) {
        return 0;
    }

    my $sum_e = List::Util::sum(@$prime_signature) || return 0;

    if ($sum_e >= Math::GMPz::Rmpz_sizeinbase($n, 2)) {
        return 0;
    }

    state $t     = Math::GMPz::Rmpz_init_nobless();
    state $count = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($count, 0);

    sub {
        my ($m, $lo, $rem_sig, $rem_sum, $j) = @_;

        Math::GMPz::Rmpz_tdiv_q($t, $n, $m);
        Math::GMPz::Rmpz_root($t, $t, $rem_sum) if ($rem_sum > 1);

        if (Math::GMPz::Rmpz_cmp_ui($t, $lo) < 0) {
            return;
        }

        Math::GMPz::Rmpz_fits_ulong_p($t) || die "Too large value!";

        my $k  = scalar(@$rem_sig);
        my $hi = Math::GMPz::Rmpz_get_ui($t);

        if ($k == 1) {

            my $pi =
              (HAS_PRIME_UTIL and $hi < PRIMECOUNT_MIN)
              ? Math::Prime::Util::prime_count($hi)
              : _prime_count($hi);

            if (FAST_MODE and $pi < ULONG_MAX) {
                Math::GMPz::Rmpz_add_ui($count, $count, $pi - $j);
            }
            else {
                Math::GMPz::Rmpz_set_str($t, "$pi", 10);
                Math::GMPz::Rmpz_sub_ui($t, $t, $j);
                Math::GMPz::Rmpz_add($count, $count, $t);
            }

            return;
        }

        my %seen;
        for my $i (0 .. $k - 1) {

            my $e = $rem_sig->[$i];
            next if $seen{$e}++;

            my $local_j = $j;
            my @new_sig = @$rem_sig;
            splice(@new_sig, $i, 1);

            if ($k == 2) {
                my $e2 = $new_sig[0];

                foreach my $p (@{_primes($lo, $hi)}) {

                    if ($e == 1) {
                        Math::GMPz::Rmpz_mul_ui($t, $m, $p);
                    }
                    else {
                        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e);
                        Math::GMPz::Rmpz_mul($t, $t, $m);
                    }

                    Math::GMPz::Rmpz_tdiv_q($t, $n, $t);
                    Math::GMPz::Rmpz_root($t, $t, $e2) if ($e2 > 1);

                    my $pi =
                      (HAS_PRIME_UTIL and Math::GMPz::Rmpz_cmp_ui($t, PRIMECOUNT_MIN) < 0)
                      ? Math::Prime::Util::prime_count(Math::GMPz::Rmpz_get_ui($t))
                      : _prime_count(Math::GMPz::Rmpz_get_str($t, 10));

                    if (FAST_MODE and $pi < ULONG_MAX) {
                        Math::GMPz::Rmpz_add_ui($count, $count, $pi - ++$local_j);
                    }
                    else {
                        Math::GMPz::Rmpz_set_str($t, "$pi", 10);
                        Math::GMPz::Rmpz_sub_ui($t, $t, ++$local_j);
                        Math::GMPz::Rmpz_add($count, $count, $t);
                    }
                }
            }
            else {

                my $u       = Math::GMPz::Rmpz_init();
                my $new_sum = $rem_sum - $e;

                for (my $p = $lo ; $p <= $hi ;) {
                    if ($e == 1) {
                        Math::GMPz::Rmpz_mul_ui($u, $m, $p);
                    }
                    else {
                        Math::GMPz::Rmpz_ui_pow_ui($t, $p, $e);
                        Math::GMPz::Rmpz_mul($u, $t, $m);
                    }
                    my $r = (HAS_PRIME_UTIL ? Math::Prime::Util::next_prime($p) : Math::Prime::Util::GMP::next_prime($p));
                    __SUB__->($u, $r, \@new_sig, $new_sum, ++$local_j);
                    $p = $r;
                }
            }
        }
      }
      ->(
        do {
            state $m = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_set_ui($m, 1);
            $m;
        },
        2,
        $prime_signature,
        $sum_e
      );

    Math::GMPz::Rmpz_fits_ulong_p($count)
      ? Math::GMPz::Rmpz_get_ui($count)
      : Math::GMPz::Rmpz_init_set($count);
}

sub prime_signature_count {
    my ($from, $to, $signature) = @_;

    Scalar::Util::reftype($signature) eq 'ARRAY'
      or die "usage: prime_signature_count(A, B, [...])";

    my @sig = map { my $v = "$_"; $v <= 0 and die "invalid prime signature (entry: $v)"; $v } @$signature;

    ref($to) eq __PACKAGE__ or _valid(\$to);

    return ZERO if $to->lt($from);

    if ($from->gt(ONE)) {
        return ((ONE)->prime_signature_count($to, $signature)->sub((ONE)->prime_signature_count($from->dec, $signature)));
    }

    my $n = _any2mpz($$to) // return ZERO;
    my $r = _prime_sig_count($n, \@sig);

    bless \$r;
}

*prime_signature_inverse_len = \&prime_signature_count;
*inverse_prime_signature_len = \&prime_signature_count;

sub _tau_partitions {
    my ($n, $max_sum_e) = @_;

    my @results;
    my @divs = _divisors($n);

    shift(@divs);    # remove divisor '1'

    my $end = $#divs;
    my @path;

    sub {
        my ($target, $min_idx, $curr_sum_e) = @_;

        if ($target == 1) {
            push @results, [@path];
            return;
        }

        for my $i ($min_idx .. $end) {
            my $d = $divs[$i];
            my $e = $d - 1;

            last if $d > $target;
            last if ($curr_sum_e + $e > $max_sum_e);

            if ($target % $d == 0) {
                push @path, $e;
                my $t =
                  HAS_PRIME_UTIL
                  ? Math::Prime::Util::divint($target, $d)
                  : Math::Prime::Util::GMP::divint($target, $d);
                __SUB__->($t, $i, $curr_sum_e + $e);
                pop @path;
            }
        }
      }
      ->($n, 0, 0);

    return @results;
}

sub tau_inverse {
    my ($from, $to, $k) = @_;

    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);

    $k = _any2ui($$k) // return _array();

    $from = _any2mpz($$from) // return _array();
    $to   = _any2mpz($$to)   // return _array();

    my @signatures = _tau_partitions($k, Math::GMPz::Rmpz_sizeinbase($to, 2) - 1);

    my @list;
    foreach my $sig (@signatures) {
        push @list, @{_sieve_prime_sig_numbers($from, $to, $sig)};
    }

    @list = map { bless \$_ } sort { $a <=> $b } @list;
    _array(\@list);
}

*inverse_tau = \&tau_inverse;

sub tau_inverse_len {
    my ($from, $to, $k) = @_;

    ref($to) eq __PACKAGE__ or _valid(\$to);
    ref($k) eq __PACKAGE__  or _valid(\$k);

    return ZERO if $to->lt($from);

    if ($from->gt(ONE)) {
        return ((ONE)->tau_inverse_len($to, $k)->sub((ONE)->tau_inverse_len($from->dec, $k)));
    }

    $k = _any2ui($$k) // return ZERO;
    my $n = _any2mpz($$to) // return ZERO;
    my @signatures = _tau_partitions($k, Math::GMPz::Rmpz_sizeinbase($n, 2) - 1);

    my @list;
    foreach my $sig (@signatures) {
        push @list, _prime_sig_count($n, $sig);
    }

    _set_int(Math::Prime::Util::GMP::vecsum(@list));
}

*inverse_tau_len = \&tau_inverse_len;

sub nth_tau_inverse {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    my $n_z = _any2mpz($$n) // goto &nan;
    my $k_z = _any2mpz($$k) // goto &nan;

    if (   Math::GMPz::Rmpz_sgn($n_z) <= 0
        or Math::GMPz::Rmpz_sgn($k_z) <= 0) {
        goto &nan;
    }

    if (Math::GMPz::Rmpz_cmp_ui($k_z, 1) == 0 and Math::GMPz::Rmpz_cmp_ui($n_z, 1) > 0) {
        goto &nan;
    }

    $n->inverse_count(sub { Sidef::Types::Number::Number::tau_inverse_len(ONE, $_[0], $k) }, 'bsearch_min');
}

sub next_tau_inverse {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_zero && return ONE;

    my $N = _big2uistr($$n) // goto &nan;
    my $K = _big2uistr($$k) // goto &nan;

    foreach my $i (1 .. Math::Prime::Util::GMP::sigma($K, 0)**2) {
        my $v = Math::Prime::Util::GMP::addint($N, $i);
        my $tau =
          (HAS_PRIME_UTIL and $v < ULONG_MAX)
          ? Math::Prime::Util::divisor_sum($v, 0)
          : Math::Prime::Util::GMP::sigma($v, 0);
        if ($tau == $K) {
            return _set_int($v);
        }
    }

    my $count = Sidef::Types::Number::Number::tau_inverse_len(ONE, $n, $k);
    $count = $count->inc;
    $count->nth_tau_inverse($k);
}

sub prev_tau_inverse {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->le(ONE) && goto &nan;

    my $N = _big2uistr($$n) // goto &nan;
    my $K = _big2uistr($$k) // goto &nan;

    foreach my $i (1 .. Math::Prime::Util::GMP::sigma($K, 0)**2) {
        my $v = Math::Prime::Util::GMP::subint($N, $i) || last;
        my $tau =
          (HAS_PRIME_UTIL and $v < ULONG_MAX)
          ? Math::Prime::Util::divisor_sum($v, 0)
          : Math::Prime::Util::GMP::sigma($v, 0);
        if ($tau == $K) {
            return _set_int($v);
        }
    }

    my $count = Sidef::Types::Number::Number::tau_inverse_len(ONE, $n, $k);
    $count = $count->dec if $n->tau->eq($k);
    $count->nth_tau_inverse($k);
}

sub semiprimes {
    (TWO)->almost_primes(@_);
}

sub carmichael {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @carmichael_numbers = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, carmichael => 1, squarefree => 1)};
#>>>

    _array(\@carmichael_numbers);
}

sub strong_fermat_carmichael {
    my ($k, $base, $from, $to) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $base = _any2ui($$base) // return _array();
    $k    = _any2ui($$k)    // return _array();

    if ($base <= 1) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @carmichael_numbers = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, fermat => $base, carmichael => 1, squarefree => 1, strong => 1)};
#>>>

    _array(\@carmichael_numbers);
}

*carmichael_strong_fermat     = \&strong_fermat_carmichael;
*carmichael_strong_fermat_psp = \&strong_fermat_carmichael;
*carmichael_strong_psp        = \&strong_fermat_carmichael;

sub lucas_carmichael {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @lucas_carmichael_numbers = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, lucas_carmichael => 1, squarefree => 1)};
#>>>

    _array(\@lucas_carmichael_numbers);
}

sub squarefree_fermat_psp {
    my ($k, $base, $from, $to) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $base = _any2ui($$base) // return _array();
    $k    = _any2ui($$k)    // return _array();

    if ($base <= 1) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @fermat_pseudoprimes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) :  _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, fermat => $base, squarefree => 1)};
#>>>

    _array(\@fermat_pseudoprimes);
}

sub squarefree_strong_fermat_psp {
    my ($k, $base, $from, $to) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $base = _any2ui($$base) // return _array();
    $k    = _any2ui($$k)    // return _array();

    if ($base <= 1) {
        return _array();
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @fermat_pseudoprimes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) :  _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, fermat => $base, squarefree => 1, strong => 1)};
#>>>

    _array(\@fermat_pseudoprimes);
}

sub almost_primes_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_cmp_ui($from, $k) <= 0 and $k > 0) {
        $from = Math::GMPz::Rmpz_init_set_ui(0);
        Math::GMPz::Rmpz_setbit($from, $k);    # from = 2**k
    }

#<<<
    _generic_each($from, $to, $block, sub {
            my ($from) = @_;

            my $lo   = Math::GMPz::Rmpz_get_d($from);
            my $step = ($k > 0) ? (_nth_almost_prime_lower($lo + CORE::log($lo) * 5e4, $k) - _nth_almost_prime_lower($lo, $k)) : 0;

            if ($step <= 0 or $step > 1e14) {
                $step = 100_000_000 * $k;
            }

            $step;
        }, sub { _sieve_almost_primes($_[0], $_[1], $k) });
#>>>
}

*each_almost_prime = \&almost_primes_each;

sub semiprimes_each {
    (TWO)->almost_primes_each(@_);
}

*each_semiprime = \&semiprimes_each;

sub nth_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k >= 1 or goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->nth_prime;
    }
    elsif ($k == 2) {
        return $n->nth_semiprime;
    }

    my $k_obj = _set_int($k);
    my $n_obj = $n;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) > 0 or do {
        return ONE if (Math::GMPz::Rmpz_sgn($n) == 0);    # not k-almost prime, but...
        goto &nan;
    };

    my $min = Math::GMPz::Rmpz_init();
    my $max = Math::GMPz::Rmpz_init_set($n);

    Math::GMPz::Rmpz_setbit($min, $k);
    Math::GMPz::Rmpz_mul_ui($max, $max, $k + 1);

    while (__cmp__(${$k_obj->almost_prime_count(bless \$max)}, $n) < 0) {
        Math::GMPz::Rmpz_set($min, $max);
        Math::GMPz::Rmpz_mul_ui($max, $max, $k);
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($max)) {
        my $r = eval { Math::Prime::Util::nth_almost_prime("$k", "$n") };
        if ($r) {    # workaround for danaj/Math-Prime-Util #71
            return _set_int("$r");
        }
    }

    my $v     = Math::GMPz::Rmpz_init();
    my $count = Math::GMPz::Rmpz_init();

    while (1) {
        Math::GMPz::Rmpz_add($v, $min, $max);
        Math::GMPz::Rmpz_div_2exp($v, $v, 1);

        $count =
          (HAS_PRIME_UTIL && Math::GMPz::Rmpz_fits_ulong_p($v))
          ? Math::GMPz::Rmpz_init_set_ui(Math::Prime::Util::almost_prime_count($k, Math::GMPz::Rmpz_get_ui($v)))
          : ${$k_obj->almost_prime_count(bless \$v)};

        my $cmp = __cmp__($count, $n);

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($max, $v, 1);
        }
        elsif ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($min, $v, 1);
        }
        else {
            last;
        }
    }

    $k_obj->almost_primes((bless \$min), (bless \$v))->last;
}

sub _almost_prime_count_cost {
    my ($n, $k) = @_;

    sub {
        my ($m, $k) = @_;

        my $s = Math::Prime::Util::GMP::rootint(Math::Prime::Util::GMP::divint($n, $m), $k);

        if ($k == 2) {
            return Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::rootint($s, (HAS_PRIME_UTIL ? 3 : 2)), Math::Prime::Util::GMP::prime_count_upper($s));
        }

        Math::Prime::Util::GMP::mulint(Math::Prime::Util::GMP::prime_count_upper($s), __SUB__->(Math::Prime::Util::GMP::mulint($m, $s), $k - 1));
      }
      ->(1, $k);
}

sub next_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->next_prime;
    }
    elsif ($k == 2) {
        return $n->next_semiprime;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set_ui(0);
    Math::GMPz::Rmpz_setbit($r, $k);

    # Smallest k-almost prime is 2^k
    if (Math::GMPz::Rmpz_cmp($n, $r) < 0) {
        return bless \$r;
    }

    if ($k <= 23) {

        # When the cost is too large, do a linear search for the next k-almost prime
        if ($k <= 20 or _almost_prime_count_cost($n, $k) >= 1e7) {

            # Optimization for native integers
            if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_slong_p($n)) {
                $n = Math::GMPz::Rmpz_get_ui($n) + 1;
                until (Math::Prime::Util::is_almost_prime($k, $n)) {
                    ++$n;
                }
                return bless \$n;
            }

            Math::GMPz::Rmpz_add_ui($r, $n, 1);

            my $r_obj = bless \$r;

            until ($r_obj->is_almost_prime($k_obj)) {
                Math::GMPz::Rmpz_add_ui($r, $r, 1);
            }

            return $r_obj;
        }
    }

    $k_obj->almost_prime_count($n_obj)->inc->nth_almost_prime($k_obj);
}

sub prev_almost_prime {
    my ($n, $k) = @_;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) || goto &nan;
    }
    else {
        $k = 2;
    }

    if ($k == 1) {
        return $n->prev_prime;
    }
    elsif ($k == 2) {
        return $n->prev_semiprime;
    }

    my $n_obj = $n;
    my $k_obj = bless \$k;

    $n = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n) < 0) {
        goto &nan;
    }

    my $r = Math::GMPz::Rmpz_init_set_ui(0);
    Math::GMPz::Rmpz_setbit($r, $k);

    # Smallest k-almost prime is 2^k
    if (Math::GMPz::Rmpz_cmp($n, $r) <= 0) {
        goto &nan;
    }

    if ($k <= 23) {

        # When the cost is too large, do a linear search for the next k-almost prime
        if ($k <= 20 or _almost_prime_count_cost($n, $k) >= 1e7) {

            # Optimization for native integers
            if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
                $n = Math::GMPz::Rmpz_get_ui($n) - 1;
                until (Math::Prime::Util::is_almost_prime($k, $n)) {
                    --$n;
                }
                return bless \$n;
            }

            Math::GMPz::Rmpz_sub_ui($r, $n, 1);

            my $r_obj = bless \$r;

            until ($r_obj->is_almost_prime($k_obj)) {
                Math::GMPz::Rmpz_sub_ui($r, $r, 1);
            }

            return $r_obj;
        }
    }

    my $count = $k_obj->almost_prime_count($n_obj);
    $count = $count->dec if $n_obj->is_almost_prime($k_obj);
    $count->nth_almost_prime($k_obj);
}

sub squarefree_almost_primes {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    $k = _any2ui($$k) // return _array();

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @squarefree_almost_primes = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_almost_primes($from, $to, $k, squarefree => 1)};
#>>>

    _array(\@squarefree_almost_primes);
}

sub squarefree_semiprimes {
    (TWO)->squarefree_almost_primes(@_);
}

sub sphenic {
    (THREE)->squarefree_almost_primes(@_);
}

*sphenic_numbers = \&sphenic;

sub sphenic_count {
    (THREE)->squarefree_almost_prime_count(@_);
}

sub squarefree_almost_primes_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    my $step = ($k > 8) ? Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10) : 1e7;

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1) });
}

*each_squarefree_almost_prime = \&squarefree_almost_primes_each;

sub squarefree_semiprimes_each {
    (TWO)->squarefree_almost_primes_each(@_);
}

*each_squarefree_semiprime = \&squarefree_semiprimes_each;

sub sphenic_each {
    (THREE)->squarefree_almost_primes_each(@_);
}

sub carmichael_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 3) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k) x (($k >> 1) + 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($k == 4) {
        $step = 5e7;
    }

    if ($k >= 5 and $step < 1e8) {
        $step = '1' . ('0' x 8);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1, carmichael => 1) });
}

*each_carmichael = \&carmichael_each;

sub carmichael_strong_fermat_each {
    my ($k, $base, $from, $to, $block) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k    = _any2ui($$k)    // return ZERO;
    $base = _any2ui($$base) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 3) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k) x ($k + 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e8) {
        $step = '1' . ('0' x 8);
    }

    if ($k == 4) {
        $step = '1' . ('0' x 9);
    }

    if ($k >= 5 and $step < 1e10) {
        $step = '1' . ('0' x 10);
    }

    if ($k >= 6 and $step < 1e11) {
        $step = '1' . ('0' x 11);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block,
                  sub { $step },
                  sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1, carmichael => 1, strong => 1, fermat => $base) });
}

*carmichael_strong_psp_each        = \&carmichael_strong_fermat_each;
*each_carmichael_strong_psp        = \&carmichael_strong_fermat_each;
*each_carmichael_strong_fermat     = \&carmichael_strong_fermat_each;
*each_carmichael_strong_fermat_psp = \&carmichael_strong_fermat_each;
*each_strong_fermat_carmichael     = \&carmichael_strong_fermat_each;
*strong_fermat_carmichael_each     = \&carmichael_strong_fermat_each;

sub lucas_carmichael_each {
    my ($k, $from, $to, $block) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 3) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k) x (($k >> 1) + 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e7) {
        $step = 1e7;
    }

    if ($k == 4) {
        $step = 5e7;
    }

    if ($k >= 5 and $step < 1e8) {
        $step = '1' . ('0' x 8);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1, lucas_carmichael => 1) });
}

*each_lucas_carmichael = \&lucas_carmichael_each;

sub squarefree_fermat_psp_each {
    my ($k, $base, $from, $to, $block) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k    = _any2ui($$k)    // return ZERO;
    $base = _any2ui($$base) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 2) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k + 1) x (($k + 1) >> 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($k == 4) {
        $step = 5e6;
    }

    if ($k >= 5 and $step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1, fermat => $base) });
}

*each_squarefree_fermat_psp = \&squarefree_fermat_psp_each;

sub squarefree_strong_fermat_psp_each {
    my ($k, $base, $from, $to, $block) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k    = _any2ui($$k)    // return ZERO;
    $base = _any2ui($$base) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 2) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k) x ($k + 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($k == 2) {
        $step = '1' . ('0' x 6);
    }

    if ($k == 4) {
        $step = '5' . ('0' x 8);
    }

    if ($k >= 5 and $step < 5e9) {
        $step = '5' . ('0' x 9);
    }

    if ($k >= 6 and $step < 2e11) {
        $step = '2' . ('0' x 11);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_almost_primes($_[0], $_[1], $k, squarefree => 1, strong => 1, fermat => $base) });
}

*each_squarefree_strong_fermat_psp = \&squarefree_strong_fermat_psp_each;

sub fermat_psp_each {
    my ($k, $base, $from, $to, $block) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k    = _any2ui($$k)    // return ZERO;
    $base = _any2ui($$base) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 1) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k + 1) x (($k + 1) >> 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($k == 4) {
        $step = '5' . ('0' x 6);
    }

    if ($k >= 5 and $step < 1e7) {
        $step = '1' . ('0' x 7);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_omega_primes($_[0], $_[1], $k, fermat => $base) });
}

*each_fermat_psp = \&fermat_psp_each;

sub strong_fermat_psp_each {
    my ($k, $base, $from, $to, $block) = @_;

    ref($base) eq __PACKAGE__ or _valid(\$base);
    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    $k    = _any2ui($$k)    // return ZERO;
    $base = _any2ui($$base) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if ($k < 1) {
        return ZERO;
    }

    # TODO: tweak the step value for better performance
    my $step = Math::Prime::Util::GMP::vecprod(($k) x ($k + 1), Math::GMPz::Rmpz_get_str(_cached_pn_primorial($k), 10));

    if ($step < 1e6) {
        $step = '1' . ('0' x 6);
    }

    if ($k == 4) {
        $step = '1' . ('0' x 7);
    }

    if ($k >= 5 and $step < 5e8) {
        $step = '5' . ('0' x 8);
    }

    if ($step > ULONG_MAX) {
        $step = Math::GMPz::Rmpz_init_set_str("$step", 10);
    }

    _generic_each($from, $to, $block, sub { $step }, sub { _sieve_omega_primes($_[0], $_[1], $k, fermat => $base, strong => 1) });
}

*each_strong_fermat_psp = \&strong_fermat_psp_each;

sub _sieve_squarefree {
    my ($from, $to) = @_;

    my @squarefree;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($to)) {
        Math::Prime::Util::forsquarefree(    # XXX: leaks memory in MPU 0.73
           sub {
               push @squarefree, $_;
           },
           Math::GMPz::Rmpz_get_ui($from),
           Math::GMPz::Rmpz_get_ui($to)
        );
    }
    else {

        my $t  = Math::GMPz::Rmpz_init_set($from);
        my @mu = Math::Prime::Util::GMP::moebius($from, $to);

        for (my $i = -1 ; ; Math::GMPz::Rmpz_add_ui($t, $t, 1)) {
            push(@squarefree, Math::GMPz::Rmpz_get_str($t, 10)) if ($mu[++$i] // last);
        }
    }

    return \@squarefree;
}

sub squarefree {
    my ($from, $to) = @_;

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return _array();
        $to   = _any2mpz($$to)   // return _array();
    }
    else {
        $to   = _any2mpz($$from) // return _array();
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($to) < 0) {
        $to = $ZERO;
    }

#<<<
    my @squarefree = map {
        (ref($_) or $_ < ULONG_MAX) ? (bless \$_) : _set_int($_)
    } @{_sieve_squarefree($from, $to)};
#>>>

    _array(\@squarefree);
}

sub squarefree_each {
    my ($from, $to, $block) = @_;

    if (defined($block)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        $from = _any2mpz($$from) // return ZERO;
        $to   = _any2mpz($$to)   // return ZERO;
    }
    else {
        $block = $to;
        $to    = _any2mpz($$from) // return ZERO;
        $from  = $ONE;
    }

    if (Math::GMPz::Rmpz_sgn($from) <= 0) {
        $from = $ONE;
    }

    _generic_each($from, $to, $block, sub { 1e4 }, sub { _sieve_squarefree($_[0], $_[1]) });
}

*each_squarefree = \&squarefree_each;

sub is_squarefree {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 0
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_square_free($n)
                        : Math::Prime::Util::GMP::moebius($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    my $size = Math::GMPz::Rmpz_sizeinbase($n, 2);

    if ($size > SMALL_NUMBER_MAX_BITS) {
        state $lim_small = _set_int(1e4);
        (bless \$n)->is_prob_squarefree($lim_small) || return $FALSE;

        if ($size > MEDIUM_NUMBER_MAX_BITS) {
            state $lim_large = _set_int(1e6);
            (bless \$n)->is_prob_squarefree($lim_large) || return $FALSE;
        }
    }

    $n = _big2uistr($n) // return $FALSE;

    _is_squarefree($n) ? $TRUE : $FALSE;
}

*is_square_free = \&is_squarefree;

sub is_powerfree {
    my ($n, $k) = @_;

    if (!defined($k)) {    # default to k = 2
        return $n->is_squarefree;
    }

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $k = _any2ui($$k) || return $FALSE;
    $n = $$n;

    if (FAST_MODE and HAS_PRIME_UTIL and !ref($n)) {
        return (($n > 0 and Math::Prime::Util::is_powerfree($n, $k)) ? $TRUE : $FALSE);
    }

    if (FAST_MODE and $k == 2 and !ref($n)) {
        return (
                (
                 $n > 0
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_square_free($n)
                        : Math::Prime::Util::GMP::moebius($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    if ($k == 2) {
        return ((bless \$n)->is_squarefree);
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return $FALSE;
    }

    if ($k == 1) {
        return $TRUE if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0);
        return $FALSE;
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return (Math::Prime::Util::is_powerfree(Math::GMPz::Rmpz_get_ui($n), $k) ? $TRUE : $FALSE);
    }

    # Optimization for large n
    if (Math::GMPz::Rmpz_sizeinbase($n, 2) > SMALL_NUMBER_MAX_BITS) {
        my ($rem, @f) = _adaptive_trial_factor($n);

        my %factors;
        ++$factors{$_} for @f;

        foreach my $e (values %factors) {
            $e < $k
              or return $FALSE;
        }

        if (Math::GMPz::Rmpz_cmp_ui($rem, 1) == 0) {
            return $TRUE;
        }

        $n = $rem;
    }

    foreach my $pp (_factor_exp($n)) {
        $pp->[1] < $k
          or return $FALSE;
    }

    return $TRUE;
}

sub is_totient {    # OEIS: A002202
    my ($x) = @_;
    $x = $$x;
    __is_int__($x)
      && Math::Prime::Util::GMP::is_totient(_big2uistr($x) // return $FALSE) ? $TRUE : $FALSE;
}

sub is_practical {    # OEIS: A005153
    my ($x) = @_;

    $x = $$x;

    # XXX: requires the GitHub version of Math::Prime::Util::GMP.
    __is_int__($x)
      && Math::Prime::Util::GMP::is_practical(_big2uistr($x) // return $FALSE) ? $TRUE : $FALSE;
}

sub is_fibonacci {    # OEIS: A010056
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;

    state $t = Math::GMPz::Rmpz_init_nobless();

    # 5*n^2 +/- 4 must be a perfect square

    Math::GMPz::Rmpz_mul($t, $n, $n);
    Math::GMPz::Rmpz_mul_ui($t, $t, 5);

    Math::GMPz::Rmpz_sub_ui($t, $t, 4);
    Math::GMPz::Rmpz_perfect_square_p($t)
      && return $TRUE;

    Math::GMPz::Rmpz_add_ui($t, $t, 8);
    Math::GMPz::Rmpz_perfect_square_p($t)
      && return $TRUE;

    $FALSE;
}

*is_fib = \&is_fibonacci;

sub fibonacci_inverse {
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return MONE;
    Math::GMPz::Rmpz_sgn($n) < 0 and return MONE;
    (bless \$n)->is_fibonacci or return MONE;

    # Handle base cases
    return ZERO if Math::GMPz::Rmpz_sgn($n) == 0;
    return ONE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;

    # Use the property:
    #   index ≈ round(log(n * sqrt(5)) / log(phi))

    local $PREC = 64;
    state $sqrt_5 = _set_int(5)->sqrt;
    state $phi    = Sidef::Types::Number::Number->phi;

    my $approx = (bless \$n)->mul($sqrt_5)->log($phi);
    my $low    = ${$approx->floor->to_i};

    $low = 0 if $low < 0;

    state $fib = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_fib_ui($fib, $low);

    if (Math::GMPz::Rmpz_cmp($fib, $n) == 0) {
        return _set_int($low);
    }

    return _set_int($low + 1);
}

*fib_inverse = \&fibonacci_inverse;

sub is_lucas {    # OEIS: A102460
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 1) >= 0
      or return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 2) <= 0
      and return $TRUE;

    if (Math::GMPz::Rmpz_fits_ulong_p($n)) {
        $n = Math::GMPz::Rmpz_get_ui($n);

        state $lookup = {'3' => undef};
        state $x      = 1;
        state $y      = 3;

        while ($y < $n) {
            ($x, $y) = ($y, $x + $y);
            undef $lookup->{$y};
        }
        if (exists $lookup->{$n}) {
            return $TRUE;
        }
        return $FALSE;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();

    if (Math::GMPz::Rmpz_sizeinbase($n, 10) <= 1000) {

        state $lookup = {'3' => undef};
        state $x      = Math::GMPz::Rmpz_init_set_ui_nobless(1);
        state $y      = Math::GMPz::Rmpz_init_set_ui_nobless(3);

        while (Math::GMPz::Rmpz_cmp($y, $n) < 0) {
            Math::GMPz::Rmpz_set($t, $y);
            Math::GMPz::Rmpz_add($y, $x, $y);
            Math::GMPz::Rmpz_set($x, $t);
            undef $lookup->{Math::GMPz::Rmpz_get_str($y, 10)};
        }

        if (exists $lookup->{Math::GMPz::Rmpz_get_str($n, 10)}) {
            return $TRUE;
        }
        return $FALSE;
    }

    state $log_phi = do {
        my $t = Math::MPFR::Rmpfr_init2_nobless(64);
        my $r = ${__PACKAGE__->phi};
        Math::MPFR::Rmpfr_log($t, $r, $ROUND);
        $t;
    };

    my $f = _any2mpfr($n) // return $FALSE;

    Math::MPFR::Rmpfr_log($f, $f, $ROUND);
    Math::MPFR::Rmpfr_div($f, $f, $log_phi, $ROUND);
    Math::MPFR::Rmpfr_round($f, $f);

    Math::GMPz::Rmpz_lucnum_ui($t, Math::MPFR::Rmpfr_get_ui($f, $ROUND));

    (Math::GMPz::Rmpz_cmp($t, $n) == 0) ? $TRUE : $FALSE;
}

sub is_cyclic {    # OEIS: A003277
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {

        $n <= 0 and return $FALSE;

        if ($n <= 3) {    # 1,2,3 are terms
            return $TRUE;
        }

        if (
            !($n & 1)                                                                 # 2 only even
            || !($n % 9)   || !($n % 25) || !($n % 49)                                # not square-free
            || !($n % 21)  || !($n % 39) || !($n % 55) || !($n % 57) || !($n % 93)    # q = 1 mod p
            || !($n % 121) || !($n % 169)                                             # not square-free
            || !($n % 111) || !($n % 129) || !($n % 155) || !($n % 183)               # q = 1 mod p
          ) {
            return $FALSE;
        }

        if (
            (
             HAS_PRIME_UTIL
             ? Math::Prime::Util::gcd(Math::Prime::Util::euler_phi($n), $n)
             : Math::Prime::Util::GMP::gcd(Math::Prime::Util::GMP::totient($n), $n)
            ) == 1
          ) {
            return $TRUE;
        }

        return $FALSE;
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return $FALSE;

    if (!Math::GMPz::Rmpz_odd_p($n)) {
        if (Math::GMPz::Rmpz_cmp_ui($n, 2) == 0) {
            return $TRUE;
        }
        return $FALSE;
    }

    foreach my $k (9, 25, 49, 21, 39, 55, 57, 93, 121, 169, 111, 129, 155, 183) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $k)) {
            return $FALSE;
        }
    }

    my $nstr = Math::GMPz::Rmpz_get_str($n, 10);

    if (Math::GMPz::Rmpz_sizeinbase($n, 10) >= 40) {
        my ($r, @factors) = _adaptive_trial_factor($n);

        my %seen;
        foreach my $p (@factors) {
            if ($seen{$p}++) {    # n must be squarefree
                return $FALSE;
            }
            if (Math::Prime::Util::GMP::gcd($nstr, $p >> 1) != 1) {
                return $FALSE;
            }
        }

        $nstr = Math::GMPz::Rmpz_get_str($r, 10);
    }

    (Math::Prime::Util::GMP::gcd($nstr, Math::Prime::Util::GMP::totient($nstr)) eq '1') ? $TRUE : $FALSE;
}

sub _verify_small_factors {
    my ($remainder_ref, $omega_ref, $check_conditions, $from, $left, $right) = @_;

    $from  //= 3;
    $left  //= 1e5;
    $right //= 1e6;

    my ($r, @factors) = _adaptive_trial_factor($$remainder_ref, $from, $left, $right);

    if (@factors) {
        $check_conditions->(@factors) || return 0;

        if (Math::GMPz::Rmpz_cmp_ui($r, 1) == 0) {
            return 1;
        }
        elsif (Math::GMPz::Rmpz_sizeinbase($r, 2) <= SMALL_NUMBER_MAX_BITS) {
            return $check_conditions->(_factor($r)) ? 1 : 0;
        }

        $$omega_ref += scalar(@factors);
        $$remainder_ref = $r;
    }

    return undef;    # inconclusive
}

# Shared logic for the final factorization and omega count verification
sub _verify_heavy_factors {
    my ($remainder, $factors_ref, $omega_ref, $check_conditions, $factor_methods) = @_;

    my @factors = @$factors_ref;

    if (scalar(@factors) == 1 and Math::GMPz::Rmpz_sizeinbase($remainder, 10) > SPECIAL_FACTORS_MIN) {
        @factors = map { $$_ } @{_set_int($remainder)->special_factors};
    }

    if (scalar(@factors) > 1) {
        my %seen;
        my @composites;

        foreach my $f (@factors) {
            return 0 if $seen{$f}++;    # not squarefree

            if (_is_prob_prime($f)) {
                $check_conditions->($f) || return 0;
                ++$$omega_ref;
            }
            else {
                push @composites, $f;
            }
        }

        return 1 unless @composites;
        @factors = @composites;
    }

    # Factorize remaining composites
    foreach my $method (@$factor_methods, sub { _factor($_[0]) }) {
        @factors = map { _is_prob_prime($_) ? $_ : $method->($_) } @factors;
    }

    $$omega_ref += scalar(@factors);

    ($$omega_ref >= 3 and $check_conditions->(@factors)) ? 1 : 0;
}

sub is_carmichael {    # OEIS: A002997
    my ($n) = @_;

    $n = $$n;

    # Fast path for native integers
    if (FAST_MODE and !ref($n)) {
        return (
                HAS_PRIME_UTIL
                ? Math::Prime::Util::is_carmichael($n)
                : Math::Prime::Util::GMP::is_carmichael($n)
               ) ? $TRUE : $FALSE;
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    # Small or even
    Math::GMPz::Rmpz_odd_p($n)            or return $FALSE;
    Math::GMPz::Rmpz_cmp_ui($n, 561) >= 0 or return $FALSE;

    state $nm1 = Math::GMPz::Rmpz_init_nobless();
    state $pm1 = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_sub_ui($nm1, $n, 1);

    # Divisible by a small square
    foreach my $k (@SMALL_PRIMES_20) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $k)) {
            return $FALSE if Math::GMPz::Rmpz_divisible_ui_p($n, $k * $k);
            return $FALSE unless Math::GMPz::Rmpz_divisible_ui_p($nm1, $k - 1);
        }
    }

    my $check_conditions = sub {
        my %seen;
        foreach my $p (@_) {
            return 0 if $seen{$p}++;    # not squarefree

            if (ref($p) eq 'Math::GMPz') {
                Math::GMPz::Rmpz_sub_ui($pm1, $p, 1);
                Math::GMPz::Rmpz_divisible_p($nm1, $pm1) || return 0;
            }
            elsif (FAST_MODE and $p < ULONG_MAX) {
                Math::GMPz::Rmpz_divisible_ui_p($nm1, $p - 1) || return 0;
            }
            else {
                Math::GMPz::Rmpz_set_str($pm1, "$p", 10);
                Math::GMPz::Rmpz_sub_ui($pm1, $pm1, 1);
                Math::GMPz::Rmpz_divisible_p($nm1, $pm1) || return 0;
            }
        }
        return 1;
    };

    my $omega     = 0;
    my $remainder = $n;

    if (Math::GMPz::Rmpz_sizeinbase($remainder, 10) >= 200) {

        my $conclusion = _verify_small_factors(\$remainder, \$omega, $check_conditions);

        if (defined($conclusion)) {
            return ($conclusion ? $TRUE : $FALSE);
        }
    }

    # Check: p^(n-1) == 1 (mod n) for random primes
    foreach my $k (1 .. 7) {
        my $p = _random_prime(CORE::int(CORE::sqrt(ULONG_MAX)));
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $p)) {
            return $FALSE if Math::GMPz::Rmpz_divisible_ui_p($n, $p * $p);
            return $FALSE unless Math::GMPz::Rmpz_divisible_ui_p($nm1, $p - 1);
        }
        else {
            Math::GMPz::Rmpz_set_ui($pm1, $p);
            Math::GMPz::Rmpz_powm($pm1, $pm1, $nm1, $n);
            return $FALSE unless Math::GMPz::Rmpz_cmp_ui($pm1, 1) == 0;
        }
    }

    # Fermat pseudoprime to base 2
    Math::GMPz::Rmpz_powm($pm1, $TWO, $nm1, $n);
    return $FALSE unless Math::GMPz::Rmpz_cmp_ui($pm1, 1) == 0;

    # Final heavy factorization via shared helper
    my @factors = _miller_factor($n);

    my @factor_methods =
      (sub { my ($n) = @_; _miller_factor(_any2mpz($n), 4 * CORE::length("$n")) }, sub { my ($n) = @_; _lucas_factor(_any2mpz($n), -1, CORE::length("$n")) },);

    _verify_heavy_factors($remainder, \@factors, \$omega, $check_conditions, \@factor_methods) ? $TRUE : $FALSE;
}

sub is_absolute_euler_psp {    # OEIS: A033181
    my ($n) = @_;

    $n = $$n;

    # Fast path for native integers
    if (FAST_MODE and !ref($n)) {
        (
         HAS_PRIME_UTIL
         ? (Math::Prime::Util::is_carmichael($n) && Math::Prime::Util::is_euler_pseudoprime($n, 2))
         : (Math::Prime::Util::GMP::is_carmichael($n) && Math::Prime::Util::GMP::is_euler_pseudoprime($n, 2))
        )
          || return $FALSE;
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    Math::GMPz::Rmpz_odd_p($n)             or return $FALSE;
    Math::GMPz::Rmpz_cmp_ui($n, 1729) >= 0 or return $FALSE;

    state $nm1   = Math::GMPz::Rmpz_init_nobless();
    state $nm1d2 = Math::GMPz::Rmpz_init_nobless();
    state $pm1   = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($nm1, $n, 1);
    Math::GMPz::Rmpz_div_2exp($nm1d2, $nm1, 1);
    return $FALSE unless Math::GMPz::Rmpz_even_p($nm1d2);

    # Divisible by a small square
    foreach my $k (@SMALL_PRIMES_20) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $k)) {
            return $FALSE if Math::GMPz::Rmpz_divisible_ui_p($n, $k * $k);
            return $FALSE unless Math::GMPz::Rmpz_divisible_ui_p($nm1d2, $k - 1);
        }
    }

    my $check_conditions = sub {
        my %seen;
        foreach my $p (@_) {
            return 0 if $seen{$p}++;    # not squarefree

            if (ref($p) eq 'Math::GMPz') {
                Math::GMPz::Rmpz_sub_ui($pm1, $p, 1);
                Math::GMPz::Rmpz_divisible_p($nm1d2, $pm1) || return 0;
            }
            elsif (FAST_MODE and $p < ULONG_MAX) {
                Math::GMPz::Rmpz_divisible_ui_p($nm1d2, $p - 1) || return 0;
            }
            else {
                Math::GMPz::Rmpz_set_str($pm1, "$p", 10);
                Math::GMPz::Rmpz_sub_ui($pm1, $pm1, 1);
                Math::GMPz::Rmpz_divisible_p($nm1d2, $pm1) || return 0;
            }
        }
        return 1;
    };

    my $omega     = 0;
    my $remainder = $n;

    if (Math::GMPz::Rmpz_sizeinbase($remainder, 10) >= 200) {

        my $conclusion = _verify_small_factors(\$remainder, \$omega, $check_conditions);

        if (defined($conclusion)) {
            return ($conclusion ? $TRUE : $FALSE);
        }
    }

    # Random primes check
    foreach my $k (1 .. 7) {
        my $p = _random_prime(CORE::int(CORE::sqrt(ULONG_MAX)));
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $p)) {
            return $FALSE if Math::GMPz::Rmpz_divisible_ui_p($n, $p * $p);
            return $FALSE unless Math::GMPz::Rmpz_divisible_ui_p($nm1d2, $p - 1);
        }
        else {
            Math::GMPz::Rmpz_set_ui($pm1, $p);
            Math::GMPz::Rmpz_powm($pm1, $pm1, $nm1d2, $n);
            return $FALSE unless (Math::GMPz::Rmpz_cmp_ui($pm1, 1) == 0 or Math::GMPz::Rmpz_cmp($pm1, $nm1) == 0);
        }
    }

    # Euler pseudoprime to base 2
    if (!Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return $FALSE unless Math::Prime::Util::GMP::is_euler_pseudoprime(Math::GMPz::Rmpz_get_str($n, 10), 2);
    }

    # Final heavy factorization via shared helper
    my @factors = _miller_factor($n);

    my @factor_methods =
      (sub { my ($n) = @_; _miller_factor(_any2mpz($n), 4 * CORE::length("$n")) }, sub { my ($n) = @_; _lucas_factor(_any2mpz($n), -1, CORE::length("$n")) },);

    _verify_heavy_factors($remainder, \@factors, \$omega, $check_conditions, \@factor_methods) ? $TRUE : $FALSE;
}

*is_abs_euler_psp = \&is_absolute_euler_psp;

sub is_imprimitive_carmichael {    # OEIS: A328935
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_cmp_ui($n, 294409) >= 0 or return $FALSE;

    if (HAS_PRIME_UTIL && Math::GMPz::Rmpz_fits_ulong_p($n)) {

        $n = Math::GMPz::Rmpz_get_ui($n);
        Math::Prime::Util::is_carmichael($n) || return $FALSE;
        my @factors = map { $_ - 1 } Math::Prime::Util::factor($n);

        my $gcd = Math::Prime::Util::gcd(@factors);
        my $lcm = Math::Prime::Util::lcm(@factors);

        return ((($gcd * $gcd) > $lcm) ? $TRUE : $FALSE);
    }

    (bless \$n)->is_carmichael() || return $FALSE;

    my @factors = map { Math::Prime::Util::GMP::subint($_, 1) } map { _factor($_) } _miller_factor($n);

    my $gcd = Math::Prime::Util::GMP::gcd(@factors);
    my $lcm = Math::Prime::Util::GMP::lcm(@factors);

    state $x = Math::GMPz::Rmpz_init_nobless();
    state $y = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_str($x, $gcd, 10);
    Math::GMPz::Rmpz_set_str($y, $lcm, 10);

    Math::GMPz::Rmpz_mul($x, $x, $x);
    Math::GMPz::Rmpz_cmp($x, $y) > 0 ? $TRUE : $FALSE;
}

sub is_lucas_carmichael {    # OEIS: A006972
    my ($n) = @_;

    $n = $$n;

    # ---------------------------------------------------------
    # Fast path for native integers
    # ---------------------------------------------------------
    if (FAST_MODE and !ref($n) and $n < ULONG_MAX) {

        return $FALSE unless $n >= 399 && $n % 2 != 0;

        foreach my $p (@SMALL_PRIMES_20) {
            if ($n % $p == 0) {
                return $FALSE if $n % ($p * $p) == 0;
                return $FALSE unless ($n + 1) % ($p + 1) == 0;
            }
        }

        my @factors =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::factor($n)
          : Math::Prime::Util::GMP::factor($n);

        return $FALSE if scalar(@factors) <= 2;

        my %seen;
        foreach my $p (@factors) {
            return $FALSE if $seen{$p}++;
            return $FALSE unless (($n + 1) % ($p + 1) == 0);
        }

        return $TRUE;
    }

    # ---------------------------------------------------------
    # GMPz Initialization & Base Checks
    # ---------------------------------------------------------
    $n = _sanitize_mpz($n) // return $FALSE;

    Math::GMPz::Rmpz_odd_p($n)            or return $FALSE;
    Math::GMPz::Rmpz_cmp_ui($n, 399) >= 0 or return $FALSE;

    state $np1 = Math::GMPz::Rmpz_init_nobless();
    state $pp1 = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_add_ui($np1, $n, 1);

    # Divisible by a small square
    foreach my $p (@SMALL_PRIMES_20) {
        if (Math::GMPz::Rmpz_divisible_ui_p($n, $p)) {
            return $FALSE if Math::GMPz::Rmpz_divisible_ui_p($n, $p * $p);
            return $FALSE unless Math::GMPz::Rmpz_divisible_ui_p($np1, $p + 1);
        }
    }

    # my $nstr = Math::GMPz::Rmpz_get_str($n, 10);

    # No Lucas-Carmichael number is known that is also a Carmichael number or a Fermat base-2 pseudoprime.
    # However, it is conjectured that infinitely many such numbers exist.

    # If there exists a squarefree composite number N such that p-1 | N-1 and
    # p+1 | N+1 for every p|N, then N must have an odd number ≥ 5 of prime factors.
    # See: https://www.sciencedirect.com/science/article/pii/S0022314X14002108

    # if (Math::Prime::Util::GMP::is_pseudoprime($nstr, 2)) {
    #     return $FALSE;     # no counter-example is known
    # }

    # Closure for Lucas-Korselt criterion: p+1 | n+1
    my $check_conditions = sub {
        my %seen;
        foreach my $p (@_) {
            return 0 if $seen{$p}++;    # not squarefree

            if (ref($p) eq 'Math::GMPz') {
                Math::GMPz::Rmpz_add_ui($pp1, $p, 1);
                Math::GMPz::Rmpz_divisible_p($np1, $pp1) || return 0;
            }
            elsif (FAST_MODE and $p < ULONG_MAX) {
                Math::GMPz::Rmpz_divisible_ui_p($np1, $p + 1) || return 0;
            }
            else {
                Math::GMPz::Rmpz_set_str($pp1, "$p", 10);
                Math::GMPz::Rmpz_add_ui($pp1, $pp1, 1);
                Math::GMPz::Rmpz_divisible_p($np1, $pp1) || return 0;
            }
        }
        return 1;
    };

    my $omega     = 0;
    my $remainder = $n;

    # ---------------------------------------------------------
    # Adaptive Trial Division
    # ---------------------------------------------------------
    if (!Math::GMPz::Rmpz_fits_ulong_p($remainder)) {

        my $conclusion = _verify_small_factors(\$remainder, \$omega, $check_conditions);

        if (defined($conclusion)) {
            return ($conclusion ? $TRUE : $FALSE);
        }
    }

    # ---------------------------------------------------------
    # Final Lucas Factorization & Verification
    # ---------------------------------------------------------
    my @factors = _lucas_factor($remainder);

    my @factor_methods = (sub { my ($n) = @_; _lucas_factor(_any2mpz($n), undef, CORE::length("$n")) },);

    _verify_heavy_factors($remainder, \@factors, \$omega, $check_conditions, \@factor_methods) ? $TRUE : $FALSE;
}

sub is_fundamental {
    my ($x) = @_;
    $x = $$x;
    __is_int__($x)
      && Math::Prime::Util::GMP::is_fundamental(_big2uistr($x) // return $FALSE) ? $TRUE : $FALSE;
}

sub is_smooth_over_prod {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _sanitize_mpz($$n) // return $FALSE;
    $k = _sanitize_mpz($$k) // return $FALSE;

    return $FALSE if Math::GMPz::Rmpz_sgn($n) <= 0;
    return $FALSE if Math::GMPz::Rmpz_sgn($k) <= 0;
    return $TRUE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;

    state $g = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_gcd($g, $n, $k);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
        return $FALSE;
    }

    my $t = Math::GMPz::Rmpz_init_set($n);

    while (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
        Math::GMPz::Rmpz_remove($t, $t, $g);
        return $TRUE if Math::GMPz::Rmpz_cmp_ui($t, 1) == 0;
        Math::GMPz::Rmpz_gcd($g, $t, $g);
    }

    return $FALSE;
}

sub is_smooth {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _sanitize_mpz($$n) // return $FALSE;
    $k = _any2ui($$k);

    if (!defined($k) or $k > 1e8) {
        return $_[0]->gpf->le($_[1]);
    }

    return $FALSE if Math::GMPz::Rmpz_sgn($n) <= 0;
    return $TRUE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;
    return $FALSE if $k <= 1;

    my $B = _cached_primorial($k);

    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($g, $n, $B);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
        return $FALSE;
    }

    my $t = Math::GMPz::Rmpz_init_set($n);

    while (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
        Math::GMPz::Rmpz_remove($t, $t, $g);
        return $TRUE if Math::GMPz::Rmpz_cmp_ui($t, 1) == 0;
        Math::GMPz::Rmpz_gcd($g, $t, $g);
    }

    return $FALSE;
}

sub is_rough {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _sanitize_mpz($$n) // return $FALSE;
    $k = _any2ui($$k);

    if (!defined($k) or $k > 1e8) {
        return $_[0]->lpf->ge($_[1]);
    }

    --$k;

    return $FALSE if Math::GMPz::Rmpz_sgn($n) <= 0;
    return $TRUE  if $k <= 1;
    return $TRUE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;

    my $B = _cached_primorial($k);

    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($g, $n, $B);

    return ((Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) ? $FALSE : $TRUE);
}

sub smooth_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->smooth_count($to)->sub($k->smooth_count($from->dec));
    }

    my $n = _any2mpz($$from) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    if ($k < 2 or Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, $k) <= 0) {
        return _set_int($n);
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        my $r = Math::Prime::Util::smooth_count(Math::GMPz::Rmpz_get_ui($n), $k);
        return bless \$r;
    }

    # Array of all primes up to $k
    my @P = @{_primes(2, $k)};

    # ========================================================================
    # TIER 1 – native (UV) arithmetic
    # psi_small($x, $i): count of P[$i]-smooth numbers ≤ $x,  $x a plain UV
    # ========================================================================
    my $psi_small = sub {
        my ($x, $i) = @_;    # $x is a native UV

        if (HAS_PRIME_UTIL) {
            return Math::Prime::Util::smooth_count($x, $P[$i]);
        }

        $x || return 0;

        # Count powers of 2 in [1..$x] = number of bits in $x.
        $i || return 1 + Math::Prime::Util::GMP::logint($x, 2);

        ($x <= $P[$i])
          ? $x
          : (__SUB__->($x, $i - 1) + __SUB__->(Math::Prime::Util::GMP::divint($x, $P[$i]), $i));
    };

    # ========================================================================
    # TIER 2 – Math::GMPz arithmetic
    # psi_big($x, $i): same, but $x is a Math::GMPz (x > ULONG_MAX)
    # ========================================================================
    my $count = sub {
        my ($x, $i) = @_;    # $x is a Math::GMPz

        # Hand off to the fast native tier as soon as possible
        if (FAST_MODE and $x < ULONG_MAX) {
            return $psi_small->($x, $i);
        }

        $x || return 0;

        # floor(log2 $x) + 1 = # of 2-smooth numbers ≤ $x.
        $i || return 1 + Math::Prime::Util::GMP::logint($x, 2);

        Math::Prime::Util::GMP::addint(__SUB__->($x, $i - 1), __SUB__->(Math::Prime::Util::GMP::divint($x, $P[$i]), $i));
      }
      ->(Math::GMPz::Rmpz_get_str($n, 10), $#P);

    _set_int($count);
}

# Finds the smallest m >= 0 such that count_func(m) >= target
sub inverse_count {
    my ($n, $count_func, $bsearch_method) = @_;
    $bsearch_method //= 'bsearch_min';

    goto &nan if $n->is_neg;

    my $lo = _set_int(0);
    my $hi = _set_int(1);

    # Exponential search to find a sufficient upper bound
    while ($count_func->($hi)->lt($n)) {
        $lo = $hi;
        $hi = $hi->mul(TWO);
    }

    $lo->$bsearch_method(
        $hi,
        Sidef::Types::Block::Block->new(
            code => sub {
                $count_func->($_[0])->cmp($n);
            }
        )
    );
}

sub rough_count {
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->rough_count($to)->sub($k->rough_count($from->dec));
    }

    my $n = _any2mpz($$from) // return ZERO;
    $k = _any2ui($$k) // return ZERO;

    if (Math::GMPz::Rmpz_sgn($n) <= 0) {
        return ZERO;
    }

    if ($k <= 2) {
        return _set_int($n);
    }

    if (Math::GMPz::Rmpz_cmp_ui($n, $k) < 0) {
        return TWO if (Math::GMPz::Rmpz_cmp_ui($n, $k) == 0);
        return ONE;
    }

    if ($k > 50 and $USE_PRIMECOUNT and Math::GMPz::Rmpz_sizeinbase($n, 2) <= 63 and $n > PRIMECOUNT_MIN) {
        my $c     = _prime_count($k - 1);
        my $count = `primecount --phi $n $c`;

        if ($? == 0 and defined($count)) {
            chomp $count;
            if ($count) {    # make sure count is not zero
                return _set_int($count);
            }
        }
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        my $r = Math::Prime::Util::legendre_phi(Math::GMPz::Rmpz_get_ui($n), Math::Prime::Util::prime_count($k - 1));
        return bless \$r;
    }

    my %cache;
    my @P = @{_primes(2, $k - 1)};

    my $result = sub {
        my ($n, $x) = @_;

        # Optimization for native n
        if (FAST_MODE and (!ref($n) or Math::GMPz::Rmpz_fits_slong_p($n))) {

            $n = Math::GMPz::Rmpz_get_ui($n) if ref($n);

            if (HAS_PRIME_UTIL) {
                return Math::Prime::Util::legendre_phi($n, $x);
            }

            return (
                sub {
                    my ($n, $x) = @_;

                    # Meissel-Lehmer truncation (The Sublinear Secret)
                    if ($P[$x - 1]**2 > $n) {
                        return _prime_count($n) - $x + 1;
                    }

                    return $cache{$x}{$n}
                      if exists $cache{$x}{$n};

                    # Initial count: odd numbers ≤ n
                    my $count = $n - ($n >> 1);

                    # Inclusion-Exclusion principle
                    for my $j (1 .. $x - 1) {
                        last if ($P[$j] > $n);
                        $count -= __SUB__->(
                                            (
                                             HAS_PRIME_UTIL
                                             ? Math::Prime::Util::divint($n, $P[$j])
                                             : Math::Prime::Util::GMP::divint($n, $P[$j])
                                            ),
                                            $j
                                           );
                    }

                    $cache{$x}{$n} = $count;
                  }
                  ->($n, $x)
            );
        }

        my $n_str = Math::GMPz::Rmpz_get_str($n, 10);

        return $cache{$x}{$n_str}
          if exists $cache{$x}{$n_str};

        # Initial count: odd numbers ≤ n
        my $count = Math::GMPz::Rmpz_init_set($n);
        Math::GMPz::Rmpz_div_2exp($count, $count, 1);
        Math::GMPz::Rmpz_sub($count, $n, $count);

        my $t = Math::GMPz::Rmpz_init();

        # Inclusion-Exclusion principle
        foreach my $j (1 .. $x - 1) {

            Math::GMPz::Rmpz_div_ui($t, $n, $P[$j]);

            my $r = __SUB__->($t, $j);

            ref($r)
              ? Math::GMPz::Rmpz_sub($count, $count, $r)
              : Math::GMPz::Rmpz_sub_ui($count, $count, $r);
        }

        $cache{$x}{$n_str} =
            Math::GMPz::Rmpz_fits_ulong_p($count)
          ? Math::GMPz::Rmpz_get_ui($count)
          : $count;
      }
      ->($n, scalar @P);

    if (ref($result)) {
        return bless \$result;
    }

    _set_int($result);
}

sub nth_smooth {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    my $n_z = _any2mpz($$n) // goto &nan;
    my $k_z = _any2mpz($$k) // goto &nan;

    if (   Math::GMPz::Rmpz_sgn($n_z) <= 0
        or Math::GMPz::Rmpz_cmp_ui($k_z, 1) <= 0) {
        goto &nan;
    }

    $n->inverse_count(sub { $k->smooth_count($_[0]) }, 'bsearch_min');
}

sub nth_rough {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    my $n_z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($n_z) <= 0) {
        goto &nan;
    }

    $n->inverse_count(sub { $k->rough_count($_[0]) }, 'bsearch_min');
}

sub next_smooth {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_zero && return ONE;
    my $count = $k->smooth_count($n);
    $count = $count->inc;
    $count->nth_smooth($k);
}

sub next_rough {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->is_zero && return ONE;
    my $count = $k->rough_count($n);
    $count = $count->inc;
    $count->nth_rough($k);
}

sub prev_smooth {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->le(ONE) && goto &nan;
    my $count = $k->smooth_count($n);
    $count = $count->dec if $n->is_smooth($k);
    $count->nth_smooth($k);
}

sub prev_rough {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $n->le(ONE) && goto &nan;
    my $count = $k->rough_count($n);
    $count = $count->dec if $n->is_rough($k);
    $count->nth_rough($k);
}

sub legendre_phi {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    $k->inc->nth_prime->rough_count($n);
}

sub smooth_part {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _sanitize_mpz($$n) // goto &nan;
    $k = _any2ui($$k)       // goto &nan;

    return ZERO if Math::GMPz::Rmpz_sgn($n) <= 0;
    return ONE  if $k <= 1;
    return ONE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;

    my $B = _cached_primorial($k);

    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($g, $n, $B);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
        return ONE;
    }

    my $t = Math::GMPz::Rmpz_init_set($n);

    while (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
        Math::GMPz::Rmpz_remove($t, $t, $g);
        return _set_int($n) if Math::GMPz::Rmpz_cmp_ui($t, 1) == 0;
        Math::GMPz::Rmpz_gcd($g, $t, $g);
    }

    Math::GMPz::Rmpz_divexact($t, $n, $t);
    bless \$t;
}

sub smooth_divisors {
    my ($k, $n) = @_;
    $k->smooth_part($n)->divisors;
}

sub rough_part {
    my ($k, $n) = @_;

    ref($n) eq __PACKAGE__ or _valid(\$n);

    $n = _sanitize_mpz($$n) // goto &nan;
    $k = _any2ui($$k)       // goto &nan;

    --$k;

    return ZERO         if Math::GMPz::Rmpz_sgn($n) <= 0;
    return _set_int($n) if $k <= 1;
    return ONE          if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;

    my $B = _cached_primorial($k);

    state $g = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_gcd($g, $n, $B);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) == 0) {
        return _set_int($n);
    }

    my $t = Math::GMPz::Rmpz_init_set($n);

    while (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {
        Math::GMPz::Rmpz_remove($t, $t, $g);
        return ONE if Math::GMPz::Rmpz_cmp_ui($t, 1) == 0;
        Math::GMPz::Rmpz_gcd($g, $t, $g);
    }

    bless \$t;
}

sub rough_divisors {
    my ($k, $n) = @_;
    $k->rough_part($n)->divisors;
}

sub is_prob_squarefree {
    my ($n, $k) = @_;

    if (!defined($k)) {
        state %cache;
        foreach my $k (2 .. 7) {

            $n->is_prob_squarefree(_set_int(10**$k))
              || return $FALSE;

            my $t = (
                $cache{$k} //= do {
                    my $z = Math::GMPz::Rmpz_init();
                    Math::GMPz::Rmpz_ui_pow_ui($z, 10, 3 * $k);
                    $z;
                }
            );

            __cmp__($$n, $t) < 0 and return $TRUE;
        }

        return $TRUE;
    }

    ref($k) eq __PACKAGE__ or _valid(\$k);
    __is_int__($$n) || return $FALSE;

    $n = _any2mpz($$n) // return $FALSE;
    $k = _any2ui($$k)  // return $FALSE;

    return $FALSE if Math::GMPz::Rmpz_sgn($n) <= 0;
    return $FALSE if $k <= 0;
    return $TRUE  if Math::GMPz::Rmpz_cmp_ui($n, 1) == 0;
    return $FALSE if Math::GMPz::Rmpz_perfect_power_p($n);

    my $B = _cached_primorial($k);

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $g = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_gcd($g, $n, $B);

    if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

        return $TRUE if Math::GMPz::Rmpz_cmp($g, $n) == 0;

        Math::GMPz::Rmpz_divexact($t, $n, $g);
        Math::GMPz::Rmpz_gcd($g, $t, $g);

        # Divisible by a small square
        return $FALSE if Math::GMPz::Rmpz_cmp_ui($g, 1) > 0;

        # k-rough part is a perfect power
        return $FALSE if Math::GMPz::Rmpz_perfect_power_p($t);
    }

    return $TRUE;
}

sub is_cubefree {
    $_[0]->is_powerfree(THREE);
}

sub is_cubefull {
    $_[0]->is_powerful(THREE);
}

sub is_squarefull {
    my ($n) = @_;
    $n->is_powerful;
}

sub __is_power__ {
    my ($n, $k) = @_;

    # $n is a Math::GMPz object
    # $k is a native signed integer

    # Everything is a first power
    $k == 1 and return 1;

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
        return 1;
    }

    # Return true when `n` is -1 and `k` is odd
    if ($k % 2 and Math::GMPz::Rmpz_cmp_si($n, -1) == 0) {
        return 1;
    }

    # Don't accept a non-positive power
    # Also, when `n` is negative and `k` is even, return faster
    if ($k <= 0 or ($k % 2 == 0 and Math::GMPz::Rmpz_sgn($n) < 0)) {
        return 0;
    }

    # Optimization for perfect squares (thanks to Dana Jacobsen)
    $k == 2 and return Math::GMPz::Rmpz_perfect_square_p($n);

    # Return faster if not a perfect power
    Math::GMPz::Rmpz_perfect_power_p($n) || return 0;

    # Check if n = a^k, for some integer `a`, by taking the k-th root of `n`
    state $t = Math::GMPz::Rmpz_init_nobless();
    !!Math::GMPz::Rmpz_root($t, $n, $k);
}

sub is_power {
    my ($n, $k) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {

        if (defined($k)) {
            ref($k) eq __PACKAGE__ or _valid(\$k);
            $k = _any2si($$k) // return $FALSE;

            if ($k == 1) {
                return $TRUE;
            }

            if ($n == 1) {
                return $TRUE;
            }

            if ($k % 2 and $n == -1) {
                return $TRUE;
            }

            if ($k <= 0 or ($k % 2 == 0 and $n < 0)) {
                return $FALSE;
            }
        }

        if ($n == 0 or $n == 1 or $n == -1) {
            return $TRUE;
        }

        my $res;
        if (defined($k) and $k == 2) {
            $res =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::is_square($n)
              : Math::Prime::Util::GMP::is_square($n);
        }
        elsif (defined($k)) {
            $res =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::is_power($n, $k)
              : Math::Prime::Util::GMP::is_power($n, $k);
        }
        else {
            $res =
              HAS_PRIME_UTIL
              ? Math::Prime::Util::is_power($n)
              : Math::Prime::Util::GMP::is_power($n);
        }

        return ($res ? $TRUE : $FALSE);
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);

        $k = _any2si($$k) // return undef;

        return (__is_power__($n, $k) ? $TRUE : $FALSE);
    }

    Math::GMPz::Rmpz_perfect_power_p($n) ? $TRUE : $FALSE;
}

*is_pp            = \&is_power;
*is_pow           = \&is_power;
*is_perfect_power = \&is_power;

sub is_prime_power {
    my ($n) = @_;

    $n = $$n;

    if (FAST_MODE and !ref($n)) {
        return (
                (
                 $n > 1
                   and (
                        HAS_PRIME_UTIL
                        ? Math::Prime::Util::is_prime_power($n)
                        : Math::Prime::Util::GMP::is_prime_power($n)
                       )
                ) ? $TRUE : $FALSE
               );
    }

    (__is_int__($n) && _prime_power_factor($n)) ? $TRUE : $FALSE;
}

sub is_square {
    my ($n, $k) = @_;

    if (FAST_MODE and !ref($$n)) {
        $n = $$n;
        $n < 0 and return $FALSE;
        my $r = (
                 HAS_PRIME_UTIL
                 ? Math::Prime::Util::is_square($n)
                 : Math::Prime::Util::GMP::is_square($n)
                );
        return ($r ? $TRUE : $FALSE);
    }

    $n->is_power(TWO);
}

*is_sqr            = \&is_square;
*is_perfect_square = \&is_square;

sub is_cube {
    $_[0]->is_power(THREE);
}

sub perfect_power_count {    # OEIS: A069623
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return ZERO;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k == 0 and return ONE;
        state $r = Math::GMPz::Rmpz_init_nobless();
        Math::GMPz::Rmpz_root($r, $n, $k);
        my $r2 =
            Math::GMPz::Rmpz_fits_ulong_p($r)
          ? Math::GMPz::Rmpz_get_ui($r)
          : Math::GMPz::Rmpz_init_set($r);
        return bless \$r2;
    }

    # Formula:
    #   a(n) = n - Sum_{k=1..floor(log_2(n))} μ(k) * (floor(n^(1/k)) - 1)
    #        = 1 - Sum_{k=2..floor(log_2(n))} μ(k) * (floor(n^(1/k)) - 1)

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set_ui($r, 0);

    foreach my $k (2 .. __ilog__($n, 2)) {
        my $mu = (HAS_PRIME_UTIL ? Math::Prime::Util::moebius($k) : Math::Prime::Util::GMP::moebius($k)) || next;
        Math::GMPz::Rmpz_root($t, $n, $k);
        Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        ($mu == 1) ? Math::GMPz::Rmpz_add($r, $r, $t) : Math::GMPz::Rmpz_sub($r, $r, $t);
    }

    Math::GMPz::Rmpz_ui_sub($r, 1, $r);
    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub perfect_power_sum {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // return ZERO;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // goto &nan;
        $k == 0 and return ONE;
        my $r = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_root($r, $n, $k);
        return ((bless \$r)->faulhaber_sum(bless \$k));
    }

    # Formula:
    #   a(n) = faulhaber(n,1) - Sum_{k=1..floor(log_2(n))} μ(k) * (faulhaber(floor(n^(1/k)),k) - 1)
    #        = 1 - Sum_{k=2..floor(log_2(n))} μ(k) * (faulhaber(floor(n^(1/k)),k) - 1)

    my $t = Math::GMPz::Rmpz_init();
    my $r = Math::GMPz::Rmpz_init_set_ui(0);

    my $t_obj = bless \$t;

    foreach my $k (2 .. __ilog__($n, 2)) {
        my $mu = (HAS_PRIME_UTIL ? Math::Prime::Util::moebius($k) : Math::Prime::Util::GMP::moebius($k)) || next;
        Math::GMPz::Rmpz_root($t, $n, $k);
        my $f = ${$t_obj->faulhaber_sum(bless \$k)} - 1;
        if (ref($f)) {
            ($mu == 1)
              ? Math::GMPz::Rmpz_add($r, $r, $f)
              : Math::GMPz::Rmpz_sub($r, $r, $f);
        }
        else {
            ($mu == 1)
              ? Math::GMPz::Rmpz_add_ui($r, $r, $f)
              : Math::GMPz::Rmpz_sub_ui($r, $r, $f);
        }
    }

    Math::GMPz::Rmpz_ui_sub($r, 1, $r);
    $r = Math::GMPz::Rmpz_get_ui($r) if Math::GMPz::Rmpz_fits_ulong_p($r);
    bless \$r;
}

sub nth_perfect_power {
    my ($n, $k) = @_;

    if (defined($k)) {
        return $n->ipow($k);
    }

    my $z = _any2mpz($$n) // goto &nan;

    if (Math::GMPz::Rmpz_sgn($z) < 0) {
        goto &nan;
    }

    # Asymptotic Formulae for the n-th Perfect Power
    # https://www.emis.de/journals/JIS/VOL15/Jakimczuk/jak29.html

    bsearch_min(
        $n,
        $n->sqr,
        Sidef::Types::Block::Block->new(
            code => sub {
                $_[0]->perfect_power_count->cmp($n);
            }
        )
    );
}

sub next_perfect_power {
    my ($n, $k) = @_;

    if (defined($k)) {
        return $n->iroot($k)->inc->ipow($k);
    }

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) >= 0 or goto &nan;

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_sqrt($r, $n);
    Math::GMPz::Rmpz_add_ui($r, $r, 1);
    Math::GMPz::Rmpz_mul($r, $r, $r);

    foreach my $k (3 .. __ilog__($n, 2) + 1) {
        Math::GMPz::Rmpz_root($t, $n, $k);
        Math::GMPz::Rmpz_add_ui($t, $t, 1);
        Math::GMPz::Rmpz_pow_ui($t, $t, $k);
        if (Math::GMPz::Rmpz_cmp($t, $r) < 0) {
            Math::GMPz::Rmpz_set($r, $t);
        }
    }

    bless \$r;
}

sub prev_perfect_power {
    my ($n, $k) = @_;

    if (defined($k)) {
        return $n->iroot($k)->dec->ipow($k);
    }

    $n = _any2mpz($$n) // goto &nan;
    Math::GMPz::Rmpz_sgn($n) > 0 or goto &nan;

    state $t = Math::GMPz::Rmpz_init_nobless();
    my $r = Math::GMPz::Rmpz_init();

    if (Math::GMPz::Rmpz_root($r, $n, 2)) {
        Math::GMPz::Rmpz_sub_ui($r, $r, 1);
    }
    Math::GMPz::Rmpz_mul($r, $r, $r);

    foreach my $k (3 .. __ilog__($n, 2)) {
        if (Math::GMPz::Rmpz_root($t, $n, $k)) {
            Math::GMPz::Rmpz_sub_ui($t, $t, 1);
        }
        Math::GMPz::Rmpz_pow_ui($t, $t, $k);
        if (Math::GMPz::Rmpz_cmp($t, $r) > 0) {
            Math::GMPz::Rmpz_set($r, $t);
        }
    }

    bless \$r;
}

sub is_power_of {
    my ($n, $k) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;
    $k = _sanitize_mpz($$k) // return $FALSE;

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
        return $TRUE;
    }

    if (Math::GMPz::Rmpz_cmp_ui($k, 2) == 0) {
        return ((Math::GMPz::Rmpz_popcount($n) == 1) ? $TRUE : $FALSE);
    }

    my $e = __ilog__($n, $k) // return $FALSE;

    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_pow_ui($t, $k, $e);

    (Math::GMPz::Rmpz_cmp($t, $n) == 0) ? $TRUE : $FALSE;
}

sub powerful_count {    # count of k-powerful numbers
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->powerful_count($to)->sub($k->powerful_count($from->dec));
    }

    my $n = _any2mpz($$from) // return ZERO;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    $k = _any2ui($$k) // return ZERO;

    if ($k <= 1) {
        return _set_int($n);
    }

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        my $r = Math::Prime::Util::powerful_count(Math::GMPz::Rmpz_get_ui($n), $k);
        return bless \$r;
    }

    _set_int(Math::Prime::Util::GMP::powerful_count(Math::GMPz::Rmpz_get_str($n, 10), $k));
}

sub squarefull_count {
    (TWO)->powerful_count(@_);
}

sub cubefull_count {
    (THREE)->powerful_count(@_);
}

sub powerful_sum {    # sum of k-powerful numbers
    my ($k, $from, $to) = @_;

    ref($from) eq __PACKAGE__ or _valid(\$from);

    if (defined($to)) {
        ref($to) eq __PACKAGE__ or _valid(\$to);
        return ZERO if $to->lt($from);
        return $k->powerful_sum($to)->sub($k->powerful_sum($from->dec));
    }

    my $n = _any2mpz($$from) // return ZERO;
    Math::GMPz::Rmpz_sgn($n) > 0 or return ZERO;

    $k = _any2ui($$k) // return ZERO;

    if ($k <= 1) {
        return ((bless \$n)->faulhaber_sum(ONE));
    }

    my $t   = Math::GMPz::Rmpz_init();
    my $sum = Math::GMPz::Rmpz_init_set_ui(0);

    my $t_obj = bless \$t;

    sub {
        my ($m, $r) = @_;

        Math::GMPz::Rmpz_div($t, $n, $m);
        Math::GMPz::Rmpz_root($t, $t, $r);

        if ($r <= $k) {
            my $w = ${$t_obj->faulhaber_sum(bless \$r)};
            if (ref($w) eq 'Math::GMPz') {
                Math::GMPz::Rmpz_addmul($sum, $m, $w);
            }
            else {
                Math::GMPz::Rmpz_addmul_ui($sum, $m, $w);
            }
            return;
        }

        my $z = Math::GMPz::Rmpz_init();

        foreach my $v (1 .. Math::GMPz::Rmpz_get_ui($t)) {

            if ($r > $k) {
                (HAS_PRIME_UTIL ? Math::Prime::Util::is_square_free($v) : Math::Prime::Util::GMP::moebius($v)) or next;
                Math::GMPz::Rmpz_gcd_ui($Math::GMPz::NULL, $m, $v) == 1                                        or next;
            }

            Math::GMPz::Rmpz_ui_pow_ui($z, $v, $r);
            Math::GMPz::Rmpz_mul($z, $z, $m);
            __SUB__->($z, $r - 1);
        }
      }
      ->($ONE, 2 * $k - 1);

    $sum = Math::GMPz::Rmpz_get_ui($sum) if Math::GMPz::Rmpz_fits_ulong_p($sum);
    bless \$sum;
}

sub squarefull_sum {
    (TWO)->powerful_sum(@_);
}

sub cubefull_sum {
    (THREE)->powerful_sum(@_);
}

sub is_powerful {
    my ($n, $k) = @_;

    $n = $$n;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2ui($$k) // return $FALSE;
        $k <= 1
          and return (__cmp__($n, 0) > 0 ? $TRUE : $FALSE);
    }
    else {
        $k = 2;
    }

    if (FAST_MODE and !ref($n) and $n < (ULONG_MAX >> 5)) {
        $n > 0 or return $FALSE;

        if (HAS_PRIME_UTIL) {
            return (Math::Prime::Util::is_powerful($n, $k) ? $TRUE : $FALSE);
        }

        foreach my $p (2, 3, 5, 7, 11, 13) {
            if ($n % $p == 0) {
                if ($n % ($p * $p) != 0) {
                    return $FALSE;
                }
                if ($k > 2) {
                    my $v    = 2;
                    my $temp = CORE::int($n / ($p * $p));
                    while ($temp % $p == 0) {
                        $v++;
                        $temp = CORE::int($temp / $p);
                    }
                    if ($v < $k) {
                        return $FALSE;
                    }
                }
            }
        }

        foreach my $pp (_factor_exp($n)) {
            $pp->[1] < $k
              and return $FALSE;
        }
        return $TRUE;
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return $FALSE;

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_set($t, $n);

    my $v2 = Math::GMPz::Rmpz_scan1($t, 0);
    if ($v2 > 0) {
        $v2 < $k and return $FALSE;
        Math::GMPz::Rmpz_tdiv_q_2exp($t, $t, $v2);
    }

    foreach my $p (3, 5, 7, 11, 13) {
        if (Math::GMPz::Rmpz_divisible_ui_p($t, $p)) {
            Math::GMPz::Rmpz_set_ui($u, $p);
            my $v = Math::GMPz::Rmpz_remove($t, $t, $u);
            $v < $k and return $FALSE;
        }
    }

    if (Math::GMPz::Rmpz_cmp_ui($t, 1) == 0) {
        return $TRUE;
    }

    Math::GMPz::Rmpz_root($u, $t, 2 * $k + 1);

    my $trial_limit = 1e6;
    if (Math::GMPz::Rmpz_fits_ulong_p($u)) {
        $trial_limit = Math::GMPz::Rmpz_get_ui($u);
        $trial_limit = 10**CORE::length(CORE::int($trial_limit));
        $trial_limit = 1e2 if ($trial_limit < 1e2);
        $trial_limit = 1e6 if ($trial_limit > 1e6);
    }

    my ($rem, @f) = _primorial_trial_factor($t, $trial_limit);

    if (@f) {
        my $current_factor = $f[0];
        my $count          = 0;

        foreach my $factor (@f) {
            if ($factor == $current_factor) {
                $count++;
            }
            else {
                $count < $k and return $FALSE;
                $current_factor = $factor;
                $count          = 1;
            }
        }

        # Catch the final block's count
        $count < $k and return $FALSE;
    }

    if (Math::GMPz::Rmpz_cmp_ui($rem, 1) == 0) {
        return $TRUE;
    }

    if (my $exp = _power_factor($rem)) {
        return (($exp >= $k) ? $TRUE : $FALSE);
    }

    if (Math::GMPz::Rmpz_cmp_ui($t, $trial_limit) < 0) {
        return $FALSE;
    }

    foreach my $pe (_factor_exp($rem)) {
        $pe->[1] < $k
          and return $FALSE;
    }

    return $TRUE;
}

sub is_achilles {
    my ($n) = @_;
    $n->is_powerful || return $FALSE;
    $n->is_power && return $FALSE;
    return $TRUE;
}

sub is_perfect {
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) > 0
      or return $FALSE;

    if (Math::GMPz::Rmpz_odd_p($n)) {    # odd case

        # References:
        #   https://www.lirmm.fr/~ochem/opn/
        #   https://en.wikipedia.org/wiki/Perfect_number#Odd_perfect_numbers

        Math::GMPz::Rmpz_sizeinbase($n, 10) > 2190
          or return $FALSE;

        Math::GMPz::Rmpz_divisible_ui_p($n, 105)
          and return $FALSE;

             Math::GMPz::Rmpz_congruent_ui_p($n, 1, 12)
          or Math::GMPz::Rmpz_congruent_ui_p($n, 117, 468)
          or Math::GMPz::Rmpz_congruent_ui_p($n, 81,  324)
          or return $FALSE;

        my $t = _set_int($n)->sigma;

        return ((__cmp__($$t, 2 * $n) == 0) ? $TRUE : $FALSE);
    }

    # Here n is even
    state $m = Math::GMPz::Rmpz_init_nobless();

    my $v     = Math::GMPz::Rmpz_remove($m, $n, $TWO);
    my $scan0 = Math::GMPz::Rmpz_scan0($m, 0);

    $scan0 == $v + 1
      or return $FALSE;

    # n must have the form: 2^(k-1)*(2^k - 1)
    $scan0 == Math::GMPz::Rmpz_popcount($m)
      or return $FALSE;

    _set_int($v + 1)->is_mersenne_prime;
}

sub is_pseudoperfect {
    my ($n) = @_;

    $n = _sanitize_mpz($$n) // return $FALSE;
    return $FALSE if Math::GMPz::Rmpz_sgn($n) <= 0;

    my @divs = map { Math::GMPz::Rmpz_init_set_str($_, 10) } _divisors(Math::GMPz::Rmpz_get_str($n, 10));
    pop @divs;    # remove $n itself

    my $sum = Math::GMPz::Rmpz_init_set_ui(0);
    foreach my $d (@divs) {
        Math::GMPz::Rmpz_add($sum, $sum, $d);
    }

    my $cmp = Math::GMPz::Rmpz_cmp($sum, $n);
    return $FALSE if $cmp < 0;     # Deficient
    return $TRUE  if $cmp == 0;    # Perfect

    # For abundant numbers, find if a subset of divisors sums to 'n'
    # We solve an equivalent problem: finding a subset of divisors that sums
    # exactly to the difference (sum - n) which represents the elements to discard.
    my $target = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_sub($target, $sum, $n);

    # Fast DP with GMP Bitsets for small abundance bounds
    if (Math::GMPz::Rmpz_cmp_ui($target, 1e5) <= 0) {
        my $target_ui = Math::GMPz::Rmpz_get_ui($target);

        state $dp = Math::GMPz::Rmpz_init_nobless();
        state $t  = Math::GMPz::Rmpz_init_nobless();

        Math::GMPz::Rmpz_set_ui($dp, 1);

        foreach my $d (@divs) {
            next if Math::GMPz::Rmpz_cmp_ui($d, $target_ui) > 0;
            my $d_ui = Math::GMPz::Rmpz_get_ui($d);
            Math::GMPz::Rmpz_mul_2exp($t, $dp, $d_ui);
            Math::GMPz::Rmpz_ior($dp, $dp, $t);
            return $TRUE if Math::GMPz::Rmpz_tstbit($dp, $target_ui);
        }
        return $FALSE;
    }

    # Recursive Pruning Search for massive targets
    @divs = CORE::reverse(@divs);

    my @mpz_obj = map { Math::GMPz::Rmpz_init() } 0 .. $#divs;

    sub {
        my ($rem_target, $idx) = @_;
        return 1 if (Math::GMPz::Rmpz_sgn($rem_target) == 0);
        return 0 if (Math::GMPz::Rmpz_sgn($rem_target) < 0 || $idx > $#divs);

        for my $i ($idx .. $#divs) {
            my $d = $divs[$i];
            next if Math::GMPz::Rmpz_cmp($rem_target, $d) < 0;

            my $new_target = $mpz_obj[$i];
            Math::GMPz::Rmpz_sub($new_target, $rem_target, $d);
            return 1 if __SUB__->($new_target, $i + 1);
        }
        return 0;
      }
      ->($target, 0) ? $TRUE : $FALSE;
}

*is_semiperfect = \&is_pseudoperfect;

sub is_weird {
    my ($n) = @_;
    $n->is_abundant || return $FALSE;
    $n->is_pseudoperfect && return $FALSE;
    return $TRUE;
}

sub _power_factor {
    my ($n) = @_;

    $n = _any2mpz($n);
    Math::GMPz::Rmpz_sgn($n) < 0 and return 0;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return Math::Prime::Util::is_power(Math::GMPz::Rmpz_get_ui($n));
    }

    my $e = 0;
    if (Math::GMPz::Rmpz_sizeinbase($n, 2) > 1e4) {

        $n = Math::GMPz::Rmpz_init_set($n);    # copy

        foreach my $i (1 .. 26) {

            my $B = _cached_primorial(1 << $i);

            state $g = Math::GMPz::Rmpz_init_nobless();
            Math::GMPz::Rmpz_gcd($g, $n, $B);

            if (Math::GMPz::Rmpz_cmp_ui($g, 1) > 0) {

                local $USE_YAFU        = 0;
                local $USE_FACTORDB    = 0;
                local $SPECIAL_FACTORS = 0;

                my @factors = _factor(Math::GMPz::Rmpz_get_str($g, 10));
                my @exponents;

                foreach my $f (@factors) {
                    Math::GMPz::Rmpz_set_ui($g, $f);
                    push @exponents, Math::GMPz::Rmpz_remove($n, $n, $g);
                }

                $e =
                  HAS_PRIME_UTIL
                  ? (Math::Prime::Util::gcd($e, @exponents))
                  : (Math::Prime::Util::GMP::gcd($e, @exponents));

                $e == 1 and return 0;

                if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
                    return $e;
                }

                if (Math::GMPz::Rmpz_sizeinbase($n, 2) < 1e4) {
                    last;
                }
            }

            last if (Math::GMPz::Rmpz_cmp($B, $n) > 0);
        }
    }

    my $e2 = Math::Prime::Util::GMP::is_power(Math::GMPz::Rmpz_get_str($n, 10));
    $e2 = Math::Prime::Util::GMP::gcd($e2, $e) if $e > 0;
    return $e2;
}

sub _prime_power_factor {
    my ($n) = @_;

    $n = _any2mpz($n);
    Math::GMPz::Rmpz_sgn($n) < 0 and return 0;

    if (HAS_PRIME_UTIL and Math::GMPz::Rmpz_fits_ulong_p($n)) {
        return Math::Prime::Util::is_prime_power(Math::GMPz::Rmpz_get_ui($n));
    }

    if (Math::GMPz::Rmpz_even_p($n)) {
        my $k = Math::GMPz::Rmpz_scan1($n, 0);
        if ($k + 1 == Math::GMPz::Rmpz_sizeinbase($n, 2)) {
            return $k;
        }
        return 0;
    }

    if (_is_prob_prime($n)) {
        return 1;
    }

    my $pow = _power_factor($n) || return 0;
    state $t = Math::GMPz::Rmpz_init_nobless();
    Math::GMPz::Rmpz_root($t, $n, $pow);
    if (_is_prob_prime(Math::GMPz::Rmpz_get_str($t, 10))) {
        return $pow;
    }
    return 0;
}

sub prime_root {
    my ($n) = @_;

    my $t   = _any2mpz($$n) // return $n;
    my $pow = _prime_power_factor($t) || return $n;

    $pow == 1 and return $n;

    my $r = Math::GMPz::Rmpz_init();

    $pow == 2
      ? Math::GMPz::Rmpz_sqrt($r, $t)
      : Math::GMPz::Rmpz_root($r, $t, $pow);

    bless \$r;
}

sub prime_power {
    my ($n) = @_;
    my $pow = _prime_power_factor($$n) || 1;
    $pow eq '1' ? ONE : (bless \$pow);
}

sub perfect_root {
    my ($n) = @_;

    my $t = _any2mpz($$n) // return $n;
    Math::GMPz::Rmpz_perfect_power_p($t) || return $n;

    my $pow = _power_factor($t) || return $n;
    my $r   = Math::GMPz::Rmpz_init();

    $pow == 2
      ? Math::GMPz::Rmpz_sqrt($r, $t)
      : Math::GMPz::Rmpz_root($r, $t, $pow);

    bless \$r;
}

sub perfect_power {
    my ($n) = @_;
    my $pow = _power_factor($$n) || return ONE;
    bless \$pow;
}

sub next_pow {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // goto &nan;
    $y = _any2mpz($$y) // goto &nan;

    Math::GMPz::Rmpz_sgn($x) <= 0 and return ONE;

    my $log = 1 + (__ilog__($x, $y) // goto &nan);

    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_fits_ulong_p($y)
      ? Math::GMPz::Rmpz_ui_pow_ui($r, Math::GMPz::Rmpz_get_ui($y), $log)
      : Math::GMPz::Rmpz_pow_ui($r, $y, $log);

    bless \$r;
}

*next_power = \&next_pow;

sub prev_pow {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = _any2mpz($$x) // goto &nan;
    $y = _any2mpz($$y) // goto &nan;

    Math::GMPz::Rmpz_sgn($x) <= 0 and goto &nan;

    my $log = (__ilog__($x, $y) // goto &nan);

    my $r = Math::GMPz::Rmpz_init();

    Math::GMPz::Rmpz_fits_ulong_p($y)
      ? Math::GMPz::Rmpz_ui_pow_ui($r, Math::GMPz::Rmpz_get_ui($y), $log)
      : Math::GMPz::Rmpz_pow_ui($r, $y, $log);

    if (Math::GMPz::Rmpz_cmp($r, $x) == 0) {

        if ($log == 0) {
            goto &nan;
        }

        Math::GMPz::Rmpz_divexact($r, $r, $y);
    }

    bless \$r;
}

*prev_power = \&prev_pow;

#
## Is a polygonal number?
#

sub __is_polygonal__ {
    my ($n, $k, $second) = @_;

    # $n is a Math::GMPz object
    # $k is a Math::GMPz object
    # $second is a boolean

    Math::GMPz::Rmpz_sgn($n) || return 1;

    # polygonal_root(n, k)
    #   = ((k - 4) ± sqrt(8 * (k - 2) * n + (k - 4)^2)) / (2 * (k - 2))

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($u, $k, 2);      # u = k-2
    Math::GMPz::Rmpz_mul($t, $n, $u);        # t = n*u
    Math::GMPz::Rmpz_mul_2exp($t, $t, 3);    # t = t*8

    Math::GMPz::Rmpz_sub_ui($u, $u, 2);      # u = u-2
    Math::GMPz::Rmpz_mul($u, $u, $u);        # u = u^2

    Math::GMPz::Rmpz_add($t, $t, $u);        # t = t+u
    Math::GMPz::Rmpz_perfect_square_p($t) || return 0;
    Math::GMPz::Rmpz_sqrt($t, $t);           # t = sqrt(t)

    Math::GMPz::Rmpz_sub_ui($u, $k, 4);      # u = k-4

    $second
      ? Math::GMPz::Rmpz_sub($t, $u, $t)     # t = t-u
      : Math::GMPz::Rmpz_add($t, $t, $u);    # t = t+u

    Math::GMPz::Rmpz_add_ui($u, $u, 2);      # u = u+2
    Math::GMPz::Rmpz_mul_2exp($u, $u, 1);    # u = u*2

    Math::GMPz::Rmpz_divisible_p($t, $u);    # true iff u|t
}

sub is_polygonal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = $$n;
    $k = $$k;

    if (FAST_MODE and !ref($n) and !ref($k) and $k >= 3) {
        return (
                (
                 HAS_PRIME_UTIL
                 ? Math::Prime::Util::is_polygonal($n, $k)
                 : Math::Prime::Util::GMP::is_polygonal($n, $k)
                ) ? $TRUE : $FALSE
               );
    }

    $n = _sanitize_mpz($n) // return $FALSE;
    $k = _sanitize_mpz($k) // return $FALSE;

    __is_polygonal__($n, $k) ? $TRUE : $FALSE;
}

sub is_polygonal2 {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _sanitize_mpz($$n) // return $FALSE;
    $k = _sanitize_mpz($$k) // return $FALSE;

    __is_polygonal__($n, $k, 1) ? $TRUE : $FALSE;
}

#
## Integer polygonal root
#

sub __ipolygonal_root__ {
    my ($n, $k, $second) = @_;

    # $n is a Math::GMPz object
    # $k is a Math::GMPz object
    # $second is a boolean

    # polygonal_root(n, k)
    #   = ((k - 4) ± sqrt(8 * (k - 2) * n + (k - 4)^2)) / (2 * (k - 2))

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($u, $k, 2);      # u = k-2
    Math::GMPz::Rmpz_mul($t, $n, $u);        # t = n*u
    Math::GMPz::Rmpz_mul_2exp($t, $t, 3);    # t = t*8

    Math::GMPz::Rmpz_sub_ui($u, $u, 2);      # u = u-2
    Math::GMPz::Rmpz_mul($u, $u, $u);        # u = u^2
    Math::GMPz::Rmpz_add($t, $t, $u);        # t = t+u

    Math::GMPz::Rmpz_sgn($t) < 0 && goto &_nan;    # `t` is negative

    Math::GMPz::Rmpz_sqrt($t, $t);                 # t = sqrt(t)
    Math::GMPz::Rmpz_sub_ui($u, $k, 4);            # u = k-4

    $second
      ? Math::GMPz::Rmpz_sub($t, $u, $t)           # t = u-t
      : Math::GMPz::Rmpz_add($t, $t, $u);          # t = t+u

    Math::GMPz::Rmpz_add_ui($u, $u, 2);            # u = u+2
    Math::GMPz::Rmpz_mul_2exp($u, $u, 1);          # u = u*2

    Math::GMPz::Rmpz_sgn($u) || return $n;         # `u` is zero

    my $r = Math::GMPz::Rmpz_init();
    Math::GMPz::Rmpz_div($r, $t, $u);              # r = floor(t/u)
    return $r;
}

#
## Integer k-gonal root of `n`
#

sub ipolygonal_root {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    bless \__ipolygonal_root__($n, $k);
}

*polygonal_iroot = \&ipolygonal_root;

#
## Second integer k-gonal root of `n`
#

sub ipolygonal_root2 {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    bless \__ipolygonal_root__($n, $k, 1);
}

*polygonal_iroot2 = \&ipolygonal_root2;

#
## n-th k-gonal number
#

sub polygonal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = $$n;
    $k = $$k;

    if (FAST_MODE and !ref($n) and !ref($k)) {
        my $t = $n * ($n * $k - $k - 2 * $n + 4);
        if (FAST_MODE and $t < ULONG_MAX and $t > LONG_MIN) {
            return
              _set_int(
                       HAS_PRIME_UTIL
                       ? Math::Prime::Util::divint($t, 2)
                       : Math::Prime::Util::GMP::divint($t, 2)
                      );
        }
    }

    $n = _any2mpz($n) // goto &nan;
    $k = _any2mpz($k) // goto &nan;

    # polygonal(n, k) = n * (n*k - k - 2*n + 4) / 2

    state $r = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_mul($r, $n, $k);         # n*k
    Math::GMPz::Rmpz_sub($r, $r, $k);         # n*k - k
    Math::GMPz::Rmpz_submul_ui($r, $n, 2);    # n*k - k - 2*n
    Math::GMPz::Rmpz_add_ui($r, $r, 4);       # n*k - k - 2*n + 4
    Math::GMPz::Rmpz_mul($r, $r, $n);         # n*(n*k - k - 2*n + 4)
    Math::GMPz::Rmpz_div_2exp($r, $r, 1);     # n*(n*k - k - 2*n + 4)/2

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

#
## n-th centered k-gonal number
#

sub centered_polygonal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    # centered_polygonal(n, k) = k*n*(n+1)/2 + 1

    state $r = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_mul($r, $n, $n);        # n*n
    Math::GMPz::Rmpz_add($r, $r, $n);        # n*(n+1)
    Math::GMPz::Rmpz_mul($r, $r, $k);        # k*n*(n+1)
    Math::GMPz::Rmpz_div_2exp($r, $r, 1);    # k*n*(n+1)/2
    Math::GMPz::Rmpz_add_ui($r, $r, 1);      # k*n*(n+1)/2 + 1

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub is_centered_polygonal {
    my ($n, $k) = @_;

    # (2*n + 1)^2 = (8*(n-1)/k + 1)

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_sub_ui($t, $n, 1);
    Math::GMPz::Rmpz_mul_2exp($t, $t, 3);
    Math::GMPz::Rmpz_divisible_p($t, $k) || return $FALSE;

    if (Math::GMPz::Rmpz_sgn($k) == 0) {
        Math::GMPz::Rmpz_cmp_ui($n, 1) == 0 and return $TRUE;
        return $FALSE;
    }

    Math::GMPz::Rmpz_divexact($t, $t, $k);
    Math::GMPz::Rmpz_add_ui($t, $t, 1);
    Math::GMPz::Rmpz_perfect_square_p($t) || return $FALSE;
    Math::GMPz::Rmpz_sqrt($t, $t);
    Math::GMPz::Rmpz_sub_ui($t, $t, 1);
    Math::GMPz::Rmpz_even_p($t) || return $FALSE;

    return $TRUE;
}

sub centered_polygonal_root {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    # r = (sqrt(8*(n-1)/k + 1)-1)/2

    $n = $$n;
    $k = $$k;

    my $t = __inc__(__div__(__mul__(__dec__($n), 8), $k));

    if (!ref($t) and (HAS_PRIME_UTIL ? Math::Prime::Util::is_square($t) : Math::Prime::Util::GMP::is_square($t))) {
        $t =
          HAS_PRIME_UTIL
          ? Math::Prime::Util::sqrtint($t)
          : Math::Prime::Util::GMP::sqrtint($t);
    }
    elsif (ref($t) eq 'Math::GMPz' and Math::GMPz::Rmpz_perfect_square_p($t)) {
        Math::GMPz::Rmpz_sqrt($t, $t);
    }
    else {
        $t = __sqrt__(_any2mpfr_mpc($t));
    }

    bless \__div__(__dec__($t), 2);
}

#
## n-th k-gonal pyramidal number
#

sub pyramidal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    # pyramidal(n, k) = (n*(n+1) * (n*(k-2) - (k-5)))/6

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_mul($t, $n, $n);
    Math::GMPz::Rmpz_add($t, $t, $n);         # t = n*(n+1)
    Math::GMPz::Rmpz_mul($r, $n, $k);         # r = n*k
    Math::GMPz::Rmpz_submul_ui($r, $n, 2);    # r = n*(k-2)
    Math::GMPz::Rmpz_sub($r, $r, $k);         # r = n*(k-2) - k
    Math::GMPz::Rmpz_add_ui($r, $r, 5);       # r = n*(k-2) - (k-5)
    Math::GMPz::Rmpz_mul($r, $r, $t);         # r = n*(n+1) * (n*(k-2) - (k-5))
    Math::GMPz::Rmpz_divexact_ui($r, $r, 6);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

#
## n-th k-gonal centered pyramidal number
#

sub centered_pyramidal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    # centered_pyramidal(n, k) = (k-1)*(n-1)*n*(n+1)/6 + n = n*(k*n^2 - k - n^2 + 7)/6

    state $r = Math::GMPz::Rmpz_init_nobless();
    state $t = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_mul($t, $n, $n);
    Math::GMPz::Rmpz_mul($r, $k, $t);           # r = k * n^2
    Math::GMPz::Rmpz_sub($r, $r, $k);           # r = k * n^2 - k
    Math::GMPz::Rmpz_sub($r, $r, $t);           # r = k * n^2 - k - n^2
    Math::GMPz::Rmpz_add_ui($r, $r, 7);         # r = k * n^2 - k - n^2 + 7
    Math::GMPz::Rmpz_mul($r, $r, $n);           # r = n*(k*n^2 - k - n^2 + 7)
    Math::GMPz::Rmpz_divexact_ui($r, $r, 6);    # r = n*(k*n^2 - k - n^2 + 7)/6

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

sub is_pyramidal {
    my ($n, $k) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);

    $n = _any2mpz($$n) // goto &nan;
    $k = _any2mpz($$k) // goto &nan;

    if (Math::GMPz::Rmpz_cmp_ui($n, 1) == 0) {
        return $TRUE;
    }

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $r = Math::GMPz::Rmpz_init_nobless();

    # When k = 2, check if n is a triangular number
    if (Math::GMPz::Rmpz_cmp_ui($k, 2) == 0) {
        return (__is_polygonal__($n, Math::GMPz::Rmpz_init_set_ui(3)) ? $TRUE : $FALSE);
    }

    Math::GMPz::Rmpz_mul_ui($t, $n, 6);
    Math::GMPz::Rmpz_sub_ui($u, $k, 2);
    Math::GMPz::Rmpz_div($u, $t, $u);

    # u = floor(((6*n)/(r-2) + floor(n^(1/3)))^(1/3))
    if (Math::GMPz::Rmpz_cmp_ui($k, 9000) <= 0) {
        Math::GMPz::Rmpz_root($r, $n, 3);
        Math::GMPz::Rmpz_add($u, $u, $r);
        Math::GMPz::Rmpz_root($u, $u, 3);
    }

    # u = round(((6*n)/(r-2))^(1/3))
    else {
        my $f = Math::MPFR::Rmpfr_init2(Math::GMPz::Rmpz_sizeinbase($n, 2));
        Math::MPFR::Rmpfr_set_z($f, $u, $round_z);
        Math::MPFR::Rmpfr_cbrt($f, $f, $round_z);
        Math::MPFR::Rmpfr_round($f, $f);
        Math::MPFR::Rmpfr_get_z($u, $f, $round_z);
    }

    Math::GMPz::Rmpz_mul($t, $u, $u);
    Math::GMPz::Rmpz_add($t, $t, $u);         # t = n*(n+1)
    Math::GMPz::Rmpz_mul($r, $u, $k);         # r = n*k
    Math::GMPz::Rmpz_submul_ui($r, $u, 2);    # r = n*(k-2)
    Math::GMPz::Rmpz_sub($r, $r, $k);         # r = n*(k-2) - k
    Math::GMPz::Rmpz_add_ui($r, $r, 5);       # r = n*(k-2) - (k-5)
    Math::GMPz::Rmpz_mul($r, $r, $t);         # r = n*(n+1) * (n*(k-2) - (k-5))
    Math::GMPz::Rmpz_divexact_ui($r, $r, 6);

    (Math::GMPz::Rmpz_cmp($r, $n) == 0) ? $TRUE : $FALSE;
}

#
## Polygonal inverses for a given number
#

sub polygonal_inverse {
    my ($n) = @_;

    $n = _any2mpz($$n) // return _array();
    Math::GMPz::Rmpz_sgn($n) > 0 or return _array();

    state $t = Math::GMPz::Rmpz_init_nobless();
    state $u = Math::GMPz::Rmpz_init_nobless();
    state $v = Math::GMPz::Rmpz_init_nobless();

    Math::GMPz::Rmpz_mul_2exp($t, $n, 1);

    my @divisors = _divisors(Math::GMPz::Rmpz_get_str($t, 10));

    shift @divisors;
    pop @divisors;

    my @inverses;

    foreach my $divisor (@divisors) {

        (FAST_MODE and $divisor < ULONG_MAX)
          ? Math::GMPz::Rmpz_set_ui($u, $divisor)
          : Math::GMPz::Rmpz_set_str($u, $divisor, 10);

        Math::GMPz::Rmpz_divexact($v, $t, $u);
        Math::GMPz::Rmpz_addmul_ui($v, $u, 2);
        Math::GMPz::Rmpz_sub_ui($v, $v, 4);
        Math::GMPz::Rmpz_sub_ui($u, $u, 1);

        if (Math::GMPz::Rmpz_divisible_p($v, $u)) {

            my $r = Math::GMPz::Rmpz_init();
            my $i = Math::GMPz::Rmpz_init();

            Math::GMPz::Rmpz_add_ui($r, $u, 1);
            Math::GMPz::Rmpz_divexact($i, $v, $u);

            push @inverses, _array([(bless \$r), (bless \$i)]);
        }
    }

    _array(\@inverses);
}

*inverse_polygonal = \&polygonal_inverse;

#
## k-gonal root of `n`
#

sub __polygonal_root__ {
    my ($n, $k, $second) = @_;
    goto($DISPATCH_TAG{ref($n)} . '__' . $DISPATCH_TAG{ref($k)});

    # polygonal_root(n, k)
    #   = ((k - 4) ± sqrt(8 * (k - 2) * n + (k - 4)^2)) / (2 * (k - 2))

  Math_MPFR__Math_MPFR: {
        my $t = Math::MPFR::Rmpfr_init2($PREC);
        my $u = Math::MPFR::Rmpfr_init2($PREC);

        Math::MPFR::Rmpfr_sub_ui($u, $k, 2, $ROUND);     # u = k-2
        Math::MPFR::Rmpfr_mul($t, $n, $u, $ROUND);       # t = n*u
        Math::MPFR::Rmpfr_mul_2ui($t, $t, 3, $ROUND);    # t = t*8

        Math::MPFR::Rmpfr_sub_ui($u, $u, 2, $ROUND);     # u = u-2
        Math::MPFR::Rmpfr_sqr($u, $u, $ROUND);           # u = u^2
        Math::MPFR::Rmpfr_add($t, $t, $u, $ROUND);       # t = t+u

        # Return a complex number for `t < 0`
        if (Math::MPFR::Rmpfr_sgn($t) < 0) {
            $n = _mpfr2mpc($n);
            $k = _mpfr2mpc($k);
            goto Math_MPC__Math_MPC;
        }

        Math::MPFR::Rmpfr_sqrt($t, $t, $ROUND);          # t = sqrt(t)
        Math::MPFR::Rmpfr_sub_ui($u, $k, 4, $ROUND);     # u = k-4

        $second
          ? Math::MPFR::Rmpfr_sub($t, $u, $t, $ROUND)     # t = u-t
          : Math::MPFR::Rmpfr_add($t, $t, $u, $ROUND);    # t = t+u

        Math::MPFR::Rmpfr_add_ui($u, $u, 2, $ROUND);      # u = u+2
        Math::MPFR::Rmpfr_mul_2ui($u, $u, 1, $ROUND);     # u = u*2

        Math::MPFR::Rmpfr_zero_p($u) && return $n;        # `u` is zero
        Math::MPFR::Rmpfr_div($t, $t, $u, $ROUND);        # t = t/u
        return $t;
    }

  Math_MPFR__Math_MPC: {
        $n = _mpfr2mpc($n);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPFR: {
        $k = _mpfr2mpc($k);
        goto Math_MPC__Math_MPC;
    }

  Math_MPC__Math_MPC: {
        my $t = Math::MPC::Rmpc_init2($PREC);
        my $u = Math::MPC::Rmpc_init2($PREC);

        Math::MPC::Rmpc_sub_ui($u, $k, 2, $ROUND);     # u = k-2
        Math::MPC::Rmpc_mul($t, $n, $u, $ROUND);       # t = n*u
        Math::MPC::Rmpc_mul_2ui($t, $t, 3, $ROUND);    # t = t*8

        Math::MPC::Rmpc_sub_ui($u, $u, 2, $ROUND);     # u = u-2
        Math::MPC::Rmpc_sqr($u, $u, $ROUND);           # u = u^2
        Math::MPC::Rmpc_add($t, $t, $u, $ROUND);       # t = t+u

        Math::MPC::Rmpc_sqrt($t, $t, $ROUND);          # t = sqrt(t)
        Math::MPC::Rmpc_sub_ui($u, $k, 4, $ROUND);     # u = k-4

        $second
          ? Math::MPC::Rmpc_sub($t, $u, $t, $ROUND)     # t = u-t
          : Math::MPC::Rmpc_add($t, $t, $u, $ROUND);    # t = t+u

        Math::MPC::Rmpc_add_ui($u, $u, 2, $ROUND);      # u = u+2
        Math::MPC::Rmpc_mul_2ui($u, $u, 1, $ROUND);     # u = u*2

        if (Math::MPC::Rmpc_cmp_si($t, 0) == 0) {       # `u` is zero
            return $n;
        }

        Math::MPC::Rmpc_div($t, $t, $u, $ROUND);        # t = t/u
        return $t;
    }
}

#
## k-gonal root of `n`
#

sub polygonal_root {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    bless \__polygonal_root__(_any2mpfr_mpc($$n), _any2mpfr_mpc($$k));
}

#
## Second k-gonal root of `n`
#

sub polygonal_root2 {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);
    bless \__polygonal_root__(_any2mpfr_mpc($$n), _any2mpfr_mpc($$k), 1);
}

sub pyramidal_root {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    # cubic_formula(-(1/3 - k/6), 1/2, -(k/6 - 5/6), -n)

    $n = $$n;
    $k = $$k;

    my $k_over_6 = __div__($k, 6);

    my $A = __add__(__div__(-1, 3), $k_over_6);
    my $B = __div__(1, 2);
    my $C = __add__(__neg__($k_over_6), __div__(5, 6));
    my $D = __neg__($n);

    my $root = [(bless \$A)->cubic_formula((bless \$B), (bless \$C), (bless \$D))]->[1];

    if (__is_real__($n) and __cmp__($n, 0) >= 0) {
        $root = $root->real;
    }

    $root;
}

sub centered_pyramidal_root {
    my ($n, $k) = @_;
    ref($k) eq __PACKAGE__ or _valid(\$k);

    # cubic_formula(k/6 - 1/6, 0, -k/6 + 7/6, -n)

    $n = $$n;
    $k = $$k;

    my $k_over_6 = __div__($k, 6);

    my $A = __sub__($k_over_6, __div__(1, 6));
    my $C = __add__(__neg__($k_over_6), __div__(7, 6));
    my $D = __neg__($n);

    my @root = (bless \$A)->cubic_formula(ZERO, (bless \$C), (bless \$D));

    if (__is_real__($n) and __cmp__($n, 0) >= 0) {
        my @pos = grep { $_->is_pos } map { $_->real } @root;
        @pos and return $pos[0];
    }

    $root[0];
}

sub is_palindrome {
    my ($n, $k) = @_;

    $n = $$n;

    if (FAST_MODE and HAS_NEW_PRIME_UTIL and !ref($n) and $n >= 0) {

        if (!defined($k)) {
            return (Math::Prime::Util::is_palindrome($n) ? $TRUE : $FALSE);
        }

        ref($k) eq __PACKAGE__ or _valid(\$k);
        if (!ref($$k) and $$k < 2147483647 and $$k >= 2) {
            return (Math::Prime::Util::is_palindrome($n, $$k) ? $TRUE : $FALSE);
        }
    }

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2mpz($$k) // return $FALSE;
    }

    $n = _sanitize_mpz($n) // return $FALSE;

    Math::GMPz::Rmpz_sgn($n) >= 0
      or return $FALSE;

    # Optimization for bases <= 62
    if (!defined($k) or Math::GMPz::Rmpz_cmp_ui($k, 62) <= 0) {

        $k = defined($k) ? Math::GMPz::Rmpz_get_ui($k) : 10;
        $k <= 1 and return $FALSE;

        my $str = Math::GMPz::Rmpz_get_str($n, $k);

        return (($str eq CORE::reverse($str)) ? $TRUE : $FALSE);
    }

    my @digits = @{$_[0]->digits($_[1])};
    my $len    = scalar(@digits) - 1;

    foreach my $i (0 .. ($len >> 1)) {
        __cmp__(${$digits[$i]}, ${$digits[$len - $i]})
          && return $FALSE;
    }

    return $TRUE;
}

*is_palindromic = \&is_palindrome;

sub next_palindrome {
    my ($n, $base) = @_;

    $base = defined($base) ? do { ref($base) eq __PACKAGE__ or _valid(\$base); _any2ui($$base) // goto &nan } : 10;
    $base <= 1 and goto &nan;

    $n = _any2mpz($$n) // goto &nan;

    Math::GMPz::Rmpz_sgn($n) >= 0
      or goto &nan;

    my @d;

    if ($base <= 10) {
        @d = split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base));
    }
    elsif ($base <= 36) {
        @d = @DIGITS_36{split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base))};
    }
    elsif ($base <= 62) {
        @d = @DIGITS_62{split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base))};
    }
    else {
        @d = map { (ref($$_) eq 'Math::GMPz') ? Math::GMPz::Rmpz_get_ui($$_) : $$_ } @{$_[0]->digits($_[1])};
    }

    my $l = $#d;
    my $i = ((scalar(@d) + 1) >> 1) - 1;

    my $is_palindrome = 1;

    foreach my $j (0 .. $i) {
        if ($d[$j] != $d[$l - $j]) {
            $is_palindrome = 0;
            last;
        }
    }

    if (!$is_palindrome) {
        my @copy = @d;

        foreach my $i (0 .. $i) {
            $d[$i] = $d[$l - $i];
        }

        my $is_greater = 1;

        foreach my $j (0 .. $i) {
            my $cmp = $d[$i - $j] <=> $copy[$i - $j];

            if ($cmp > 0) {
                last;
            }
            if ($cmp < 0) {
                $is_greater = 0;
                last;
            }
        }

        if ($is_greater) {
            return _set_int(__digits2num__($base, \@d));
        }
    }

    while ($i >= 0 and $d[$i] == $base - 1) {
        $d[$i] = 0;
        $d[$l - $i] = 0;
        $i--;
    }

    if ($i >= 0) {
        $d[$i]++;
        $d[$l - $i] = $d[$i];
    }
    else {
        @d     = (0) x (scalar(@d) + 1);
        $d[0]  = 1;
        $d[-1] = 1;
    }

    _set_int(__digits2num__($base, \@d));
}

sub reverse {
    my ($n, $k) = @_;

    $n = _any2mpz($$n) // goto &nan;

    if (defined($k)) {
        ref($k) eq __PACKAGE__ or _valid(\$k);
        $k = _any2mpz($$k) // goto &nan;
    }

    # Optimization for bases <= 62
    if (!defined($k) or Math::GMPz::Rmpz_cmp_ui($k, 62) <= 0) {

        $k = defined($k) ? Math::GMPz::Rmpz_get_ui($k) : 10;
        $k <= 1 and goto &nan;

        my $str = scalar(CORE::reverse(Math::GMPz::Rmpz_get_str($n, $k))) =~ s/^0+//r;

        $str || return ZERO;

        if (substr($str, -1) eq '-') {    # support for negative numbers
            chop($str);
            $str = "-$str";
        }

        if ($k == 10) {
            return _set_int($str);
        }

        return bless \(my $value = Math::GMPz::Rmpz_init_set_str("$str", $k));
    }

    $_[0]->digits($_[1])->flip->digits2num($_[1])->mul($_[0]->sgn);
}

*flip = \&reverse;

sub rotate {
    my ($n, $k, $base) = @_;

    ref($k) eq __PACKAGE__ or _valid(\$k);
    (ref($base) eq __PACKAGE__ or _valid(\$base)) if defined($base);

    $n->digits($base)->rotate($k->neg)->digits2num($base);
}

sub shift_left {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if ($y == 0) {
        return bless \$x;
    }

    if (FAST_MODE and $y >= 0 and !ref($x) and $x >= 0) {

        if ($x == 0) {
            return bless \$x;
        }

        if ($] >= 5.024000 and CORE::log($x) + CORE::log(2) * $y < CORE::log(ULONG_MAX)) {
            my $r = $x << $y;
            return bless \$r;
        }
    }

    $x = _any2mpz($x) // (goto &nan);

    my $r = Math::GMPz::Rmpz_init();

    ($y < 0)
      ? Math::GMPz::Rmpz_div_2exp($r, $x, -$y)
      : Math::GMPz::Rmpz_mul_2exp($r, $x, $y);

    bless \$r;
}

*lsft = \&shift_left;

sub shift_right {
    my ($x, $y) = @_;

    ref($y) eq __PACKAGE__ or _valid(\$y);

    $x = $$x;
    $y = $$y;

    $y = _any2si($y) // (goto &nan) if ref($y);

    if (FAST_MODE and $y >= 0 and !ref($x) and $x >= 0) {

        if ($y >= $x) {
            return ZERO;
        }

        if ($y == 0) {
            return bless \$x;
        }

        # Fails on (old?) 32-bit perl with use64bitint=undef
        # perl -E 'say (112 >> 32)'   # incorrectly prints 112 instead of 0
        # https://www.cpantesters.org/cpan/report/38630124-4799-11ee-98b0-b3c3213a625c

        # This issue has been fixed in perl-5.24.0
        # https://perldoc.perl.org/5.24.0/perldelta#Integer-shift-(%3C%3C-and-%3E%3E)-now-more-explicitly-defined
        if ($] >= 5.024000 or $y < 32) {
            my $r = $x >> $y;
            return bless \$r;
        }
    }

    $x = _any2mpz($x) // (goto &nan);

    state $r = Math::GMPz::Rmpz_init_nobless();

    ($y < 0)
      ? Math::GMPz::Rmpz_mul_2exp($r, $x, -$y)
      : Math::GMPz::Rmpz_div_2exp($r, $x, $y);

    my $r2 =
        Math::GMPz::Rmpz_fits_ulong_p($r)
      ? Math::GMPz::Rmpz_get_ui($r)
      : Math::GMPz::Rmpz_init_set($r);
    bless \$r2;
}

*rsft = \&shift_right;

#
## Rational specific
#

sub numerator {
    my ($x) = @_;

    my $r = $$x;
    while (1) {

        if (ref($r) eq 'Math::GMPq') {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPq::Rmpq_get_num($z, $r);
            return bless \$z;
        }

        ref($r) eq 'Math::GMPz' and return $x;    # is an integer

        $r = _any2mpq($r) // (goto &nan);
    }
}

*nu = \&numerator;

sub denominator {
    my ($x) = @_;

    my $r = $$x;
    while (1) {

        if (ref($r) eq 'Math::GMPq') {
            my $z = Math::GMPz::Rmpz_init();
            Math::GMPq::Rmpq_get_den($z, $r);
            return bless \$z;
        }

        ref($r) eq 'Math::GMPz' and return ONE;    # is an integer

        $r = _any2mpq($r) // (goto &nan);
    }
}

*de = \&denominator;

sub nude {
    ($_[0]->numerator, $_[0]->denominator);
}

#
## Conversion/Miscellaneous
#

sub chr {
    my ($x) = @_;
    Sidef::Types::String::String->new(CORE::chr(__numify__($$x)));
}

sub __round__ {
    my ($x, $prec) = @_;

    goto($DISPATCH_TAG{ref($x)});

  Scalar: {
        $x = _any2mpz($x);
        goto Math_GMPz;
    }

  Math_MPFR: {
        my $nth = -CORE::int($prec);

        my $p = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        Math::MPFR::Rmpfr_set_str($p, '1e' . CORE::abs($nth), 10, $ROUND);

        my $r = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        if ($nth < 0) {
            Math::MPFR::Rmpfr_div($r, $x, $p, $ROUND);
        }
        else {
            Math::MPFR::Rmpfr_mul($r, $x, $p, $ROUND);
        }

        Math::MPFR::Rmpfr_round($r, $r);

        if ($nth < 0) {
            Math::MPFR::Rmpfr_mul($r, $r, $p, $ROUND);
        }
        else {
            Math::MPFR::Rmpfr_div($r, $r, $p, $ROUND);
        }

        return $r;
    }

  Math_MPC: {
        my $real = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
        my $imag = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

        Math::MPC::RMPC_RE($real, $x);
        Math::MPC::RMPC_IM($imag, $x);

        $real = __SUB__->($real, $prec);
        $imag = __SUB__->($imag, $prec);

        if (Math::MPFR::Rmpfr_zero_p($imag)) {
            return $real;
        }

        my $r = Math::MPC::Rmpc_init2(CORE::int($PREC));
        Math::MPC::Rmpc_set_fr_fr($r, $real, $imag, $ROUND);
        return $r;
    }

  Math_GMPq: {
        my $nth = -CORE::int($prec);

        my $r = Math::GMPq::Rmpq_init();
        Math::GMPq::Rmpq_set($r, $x);

        my $sgn = Math::GMPq::Rmpq_sgn($r);

        if ($sgn < 0) {
            Math::GMPq::Rmpq_neg($r, $r);
        }

        my $p = Math::GMPz::Rmpz_init_set_str('1' . ('0' x CORE::abs($nth)), 10);

        if ($nth < 0) {
            Math::GMPq::Rmpq_div_z($r, $r, $p);
        }
        else {
            Math::GMPq::Rmpq_mul_z($r, $r, $p);
        }

        state $half = do {
            my $q = Math::GMPq::Rmpq_init_nobless();
            Math::GMPq::Rmpq_set_ui($q, 1, 2);
            $q;
        };

        Math::GMPq::Rmpq_add($r, $r, $half);

        my $z = Math::GMPz::Rmpz_init();
        Math::GMPz::Rmpz_set_q($z, $r);

        if (Math::GMPz::Rmpz_odd_p($z) and Math::GMPq::Rmpq_integer_p($r)) {
            Math::GMPz::Rmpz_sub_ui($z, $z, 1);
        }

        Math::GMPq::Rmpq_set_z($r, $z);

        if ($nth < 0) {
            Math::GMPq::Rmpq_mul_z($r, $r, $p);
        }
        else {
            Math::GMPq::Rmpq_div_z($r, $r, $p);
        }

        if ($sgn < 0) {
            Math::GMPq::Rmpq_neg($r, $r);
        }

        if (Math::GMPq::Rmpq_integer_p($r)) {
            Math::GMPz::Rmpz_set_q($z, $r);
            return $z;
        }

        return $r;
    }

  Math_GMPz: {
        return $x if ($prec == 0);
        $x = _mpz2mpq($x);
        goto Math_GMPq;
    }
}

sub round {
    my ($x, $prec) = @_;

    my $nth = (
        defined($prec)
        ? do {
            ref($prec) eq __PACKAGE__ or _valid(\$prec);
            _any2si($$prec) // (goto &nan);
          }
        : 0
    );

    bless \__round__($$x, $nth);
}

*roundf = \&round;

sub to {
    my ($from, $to, $step) = @_;
    Sidef::Types::Range::RangeNumber->new($from, $to, $step // ONE);
}

*upto = \&to;

sub downto {
    my ($from, $to, $step) = @_;
    Sidef::Types::Range::RangeNumber->new($from, $to, defined($step) ? $step->neg : MONE);
}

sub xto {
    my ($from, $to, $step) = @_;

    $to = defined($step) ? $to->sub($step) : $to->dec;

    Sidef::Types::Range::RangeNumber->new($from, $to, $step // ONE);
}

*xupto = \&xto;

sub xdownto {
    my ($from, $to, $step) = @_;

    $from = defined($step) ? $from->sub($step) : $from->dec;

    Sidef::Types::Range::RangeNumber->new($from, $to, defined($step) ? $step->neg : MONE);
}

sub range {
    my ($from, $to, $step) = @_;

    defined($to) ? $from->to($to, $step) : (ZERO)->to($from->dec);
}

{
    my $srand = (HAS_PRIME_UTIL ? Math::Prime::Util::urandomm(ULONG_MAX) : Math::Prime::Util::GMP::urandomm(ULONG_MAX));

    {
        state $state = Math::MPFR::Rmpfr_randinit_mt_nobless();
        Math::MPFR::Rmpfr_randseed_ui($state, $srand);

        sub rand {
            my ($x, $y) = @_;

            my $rand = Math::MPFR::Rmpfr_init2(CORE::int($PREC));

            if (defined($y)) {
                ref($y) eq __PACKAGE__ or _valid(\$y);
                Math::MPFR::Rmpfr_urandom($rand, $state, $ROUND);
                $rand = __mul__($rand, __sub__($$y, $$x));
                $rand = __add__($rand, $$x);
            }
            else {
                Math::MPFR::Rmpfr_urandom($rand, $state, $ROUND);
                $rand = __mul__($rand, $$x);
            }
            bless \$rand;
        }

        sub seed {
            my ($x) = @_;
            my $z = _any2mpz($$x) // die "[ERROR] Number.seed(): invalid seed value <<$x>> (expected an integer)";
            Math::MPFR::Rmpfr_randseed($state, $z);
            bless \$z;
        }
    }
}

sub of {
    my ($x, $obj, $range) = @_;

    if (defined($range) and ref($obj) eq 'Sidef::Types::Block::Block') {
        return $range->lazy->map($obj)->first($x);
    }

    $x = CORE::int(__numify__($$x));

    if (ref($obj) eq 'Sidef::Types::Block::Block') {
        my @array;
        for (my $i = 0 ; $i < $x ; ++$i) {
            push @array, $obj->run($i < SMALL_INTS_SIZE ? $SMALL_INTS[$i] : bless(\(my $o = $i)));
        }
        return _array(\@array);
    }

    _array([($obj) x $x]);
}

sub by {
    my ($x, $block, $range) = @_;

    if (defined($range) and ref($block) eq 'Sidef::Types::Block::Block') {
        return $block->first($x, $range);
    }

    $x = CORE::int(__numify__($$x));

    my @items;
    for (my ($i, $j) = (0, 0) ; $j < $x ; ++$i) {
        my $k = $i < SMALL_INTS_SIZE ? $SMALL_INTS[$i] : bless(\(my $o = $i));
        if ($block->run($k)) {
            push @items, $k;
            ++$j;
        }
    }

    _array(\@items);
}

sub defs {
    my ($x, $block, $range) = @_;

    if (defined($range) and ref($block) eq 'Sidef::Types::Block::Block') {
        state $defined = Sidef::Types::Block::Block->new(code => sub { defined($_[0]) });
        return $range->lazy->map($block)->grep($defined)->first($x);
    }

    $x = CORE::int(__numify__($$x));

    my @items;
    for (my ($i, $j) = (0, 0) ; $j < $x ; ++$i) {
        push @items, $block->run($i < SMALL_INTS_SIZE ? $SMALL_INTS[$i] : bless(\(my $o = $i))) // next;
        ++$j;
    }

    _array(\@items);
}

sub times {
    my ($x, $block) = @_;

    $x = CORE::int(__numify__($$x));

    for (my $i = 0 ; $i < $x ; ++$i) {
        $block->run($i < SMALL_INTS_SIZE ? $SMALL_INTS[$i] : bless(\(my $o = $i)));
    }

    return $_[0];
}

sub th {
    my ($n, $block, $range) = @_;

    if (ref($block) ne 'Sidef::Types::Block::Block') {
        return undef;
    }

    $block->nth($n, $range);
}

*st = \&th;
*nd = \&th;
*rd = \&th;

foreach my $name (
                  qw(
                  permutations
                  circular_permutations
                  derangements
                  )
  ) {
    no strict 'refs';
    *{__PACKAGE__ . '::' . $name} = sub {
        my ($n, $block) = @_;
        _array([map { $_ < SMALL_INTS_SIZE ? $SMALL_INTS[$_] : bless \$_ } 0 .. __numify__($$n) - 1])->$name($block);
    };
}

*complete_permutations = \&derangements;

foreach my $name (
                  qw(
                  subsets
                  variations
                  variations_with_repetition
                  combinations
                  combinations_with_repetition
                  )
  ) {
    no strict 'refs';
    *{__PACKAGE__ . '::' . $name} = sub {
        my ($n, $k, $block) = @_;
        _array([map { $_ < SMALL_INTS_SIZE ? $SMALL_INTS[$_] : bless \$_ } 0 .. __numify__($$n) - 1])->$name($k, $block);
    };
}

*tuples                 = \&variations;
*tuples_with_repetition = \&variations_with_repetition;

sub bsearch_inverse {
    my ($min, $max, $block) = @_;

    my $prec = CORE::int($PREC);

    my $left   = Math::MPFR::Rmpfr_init2($prec);
    my $right  = Math::MPFR::Rmpfr_init2($prec);
    my $middle = Math::MPFR::Rmpfr_init2($prec);

    if (defined($block)) {
        ref($max) eq __PACKAGE__ or _valid(\$max);
        Math::MPFR::Rmpfr_set($left,  (_any2mpfr($$min) // return undef), $ROUND);
        Math::MPFR::Rmpfr_set($right, (_any2mpfr($$max) // return undef), $ROUND);
    }
    else {
        $block = $max;
        Math::MPFR::Rmpfr_set($right, (_any2mpfr($$min) // return undef), $ROUND);
        Math::MPFR::Rmpfr_set_ui($left, 0, $ROUND);
    }

    my $prev;

    while (1) {

        Math::MPFR::Rmpfr_add($middle, $left, $right, $ROUND);
        Math::MPFR::Rmpfr_div_2ui($middle, $middle, 1, $ROUND);

        my $item = Math::MPFR::Rmpfr_init2($prec);
        Math::MPFR::Rmpfr_set($item, $middle, $ROUND);

        my $value = bless(\$item, __PACKAGE__);
        my $cmp   = CORE::int($block->run($value)) || return $value;

        if ($cmp > 0) {
            Math::MPFR::Rmpfr_set($right, $middle, $ROUND);
        }
        elsif ($cmp < 0) {
            Math::MPFR::Rmpfr_set($left, $middle, $ROUND);
        }

        if (Math::MPFR::Rmpfr_cmp($left, $right) >= 0) {
            last;
        }

        # Prevent infinite looping
        if (defined($prev) and Math::MPFR::Rmpfr_cmp($prev, $item) == 0) {
            return $value;
        }

        $prev = $item;
    }

    return undef;
}

*bsearch_solve = \&bsearch_inverse;

sub bsearch {
    my ($left, $right, $block) = @_;

    if (defined($block)) {
        ref($right) eq __PACKAGE__ or _valid(\$right);
        $left  = Math::GMPz::Rmpz_init_set(_any2mpz($$left)  // return undef);
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$right) // return undef);
    }
    else {
        $block = $right;
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$left) // return undef);
        $left  = Math::GMPz::Rmpz_init_set_ui(0);
    }

    my $middle = Math::GMPz::Rmpz_init();

    while (Math::GMPz::Rmpz_cmp($left, $right) <= 0) {

        Math::GMPz::Rmpz_add($middle, $left, $right);
        Math::GMPz::Rmpz_div_2exp($middle, $middle, 1);

        my $item = bless \(my $value = Math::GMPz::Rmpz_init_set($middle));
        my $cmp  = CORE::int($block->run($item)) || return $item;

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($right, $middle, 1);
        }
        else {
            Math::GMPz::Rmpz_add_ui($left, $middle, 1);
        }
    }

    return undef;
}

sub bsearch_ge {
    my ($left, $right, $block) = @_;

    if (defined($block)) {
        ref($right) eq __PACKAGE__ or _valid(\$right);
        $left  = Math::GMPz::Rmpz_init_set(_any2mpz($$left)  // return undef);
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$right) // return undef);
    }
    else {
        $block = $right;
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$left) // return undef);
        $left  = Math::GMPz::Rmpz_init_set_ui(0);
    }

    my $middle = Math::GMPz::Rmpz_init();

    while (1) {

        Math::GMPz::Rmpz_add($middle, $left, $right);
        Math::GMPz::Rmpz_div_2exp($middle, $middle, 1);

        my $item = bless \(my $value = Math::GMPz::Rmpz_init_set($middle));
        my $cmp  = CORE::int($block->run($item)) || return $item;

        if ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($left, $middle, 1);

            if (Math::GMPz::Rmpz_cmp($left, $right) > 0) {
                Math::GMPz::Rmpz_add_ui($middle, $middle, 1);
                last;
            }
        }
        else {
            Math::GMPz::Rmpz_sub_ui($right, $middle, 1);
            Math::GMPz::Rmpz_cmp($left, $right) > 0 and last;
        }
    }

    bless \$middle;
}

sub bsearch_le {
    my ($left, $right, $block) = @_;

    if (defined($block)) {
        ref($right) eq __PACKAGE__ or _valid(\$right);
        $left  = Math::GMPz::Rmpz_init_set(_any2mpz($$left)  // return undef);
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$right) // return undef);
    }
    else {
        $block = $right;
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$left) // return undef);
        $left  = Math::GMPz::Rmpz_init_set_ui(0);
    }

    my $middle = Math::GMPz::Rmpz_init();

    while (1) {

        Math::GMPz::Rmpz_add($middle, $left, $right);
        Math::GMPz::Rmpz_div_2exp($middle, $middle, 1);

        my $item = bless \(my $value = Math::GMPz::Rmpz_init_set($middle));
        my $cmp  = CORE::int($block->run($item)) || return $item;

        if ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($left, $middle, 1);
            Math::GMPz::Rmpz_cmp($left, $right) > 0 and last;
        }
        else {
            Math::GMPz::Rmpz_sub_ui($right, $middle, 1);
            if (Math::GMPz::Rmpz_cmp($left, $right) > 0) {
                Math::GMPz::Rmpz_sub_ui($middle, $middle, 1);
                last;
            }
        }
    }

    bless \$middle;
}

sub bsearch_min {
    my ($left, $right, $block) = @_;

    if (defined($block)) {
        ref($right) eq __PACKAGE__ or _valid(\$right);
        $left  = Math::GMPz::Rmpz_init_set(_any2mpz($$left)  // return undef);
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$right) // return undef);
    }
    else {
        $block = $right;
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$left) // return undef);
        $left  = Math::GMPz::Rmpz_init_set_ui(0);
    }

    my $middle = Math::GMPz::Rmpz_init();

    while (Math::GMPz::Rmpz_cmp($left, $right) < 0) {

        Math::GMPz::Rmpz_add($middle, $left, $right);
        Math::GMPz::Rmpz_div_2exp($middle, $middle, 1);

        my $item = bless \(my $value = Math::GMPz::Rmpz_init_set($middle));
        my $cmp  = CORE::int($block->run($item));

        if ($cmp < 0) {
            Math::GMPz::Rmpz_add_ui($left, $middle, 1);
        }
        else {
            Math::GMPz::Rmpz_set($right, $middle);
        }
    }

    bless \$left;
}

sub bsearch_max {
    my ($left, $right, $block) = @_;

    if (defined($block)) {
        ref($right) eq __PACKAGE__ or _valid(\$right);
        $left  = Math::GMPz::Rmpz_init_set(_any2mpz($$left)  // return undef);
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$right) // return undef);
    }
    else {
        $block = $right;
        $right = Math::GMPz::Rmpz_init_set(_any2mpz($$left) // return undef);
        $left  = Math::GMPz::Rmpz_init_set_ui(0);
    }

    my $middle = Math::GMPz::Rmpz_init();

    while (Math::GMPz::Rmpz_cmp($left, $right) < 0) {

        Math::GMPz::Rmpz_add($middle, $left, $right);
        Math::GMPz::Rmpz_div_2exp($middle, $middle, 1);
        Math::GMPz::Rmpz_add_ui($middle, $middle, 1);

        my $item = bless \(my $value = Math::GMPz::Rmpz_init_set($middle));
        my $cmp  = CORE::int($block->run($item));

        if ($cmp > 0) {
            Math::GMPz::Rmpz_sub_ui($right, $middle, 1);
        }
        else {
            Math::GMPz::Rmpz_set($left, $middle);
        }
    }

    bless \$right;
}

sub commify {
    my ($self) = @_;

    my $n = "$self";

    my $x   = $n;
    my $neg = $n =~ s{^-}{};
    $n =~ /\.|$/;

    if ($-[0] > 3) {

        my $l = $-[0] - 3;
        my $i = ($l - 1) % 3 + 1;

        $x = substr($n, 0, $i) . ',';

        while ($i < $l) {
            $x .= substr($n, $i, 3) . ',';
            $i += 3;
        }

        $x .= substr($n, $i);
    }

    Sidef::Types::String::String->new(($neg ? '-' : '') . $x);
}

#
## Conversions
#

sub rad2deg {
    my ($x) = @_;
    my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_pi($f, $ROUND);
    Math::MPFR::Rmpfr_ui_div($f, 180, $f, $ROUND);
    bless \__mul__($f, $$x);
}

sub deg2rad {
    my ($x) = @_;
    my $f = Math::MPFR::Rmpfr_init2(CORE::int($PREC));
    Math::MPFR::Rmpfr_const_pi($f, $ROUND);
    Math::MPFR::Rmpfr_div_ui($f, $f, 180, $ROUND);
    bless \__mul__($f, $$x);
}

# Precomputed pi(n) values — see source for references to OEIS sequences.
%PRIMEPI_LOOKUP = (

    # Powers of 10 — OEIS A006880
    "1000000"                        => "78498",
    "10000000"                       => "664579",
    "100000000"                      => "5761455",
    "1000000000"                     => "50847534",
    "10000000000"                    => "455052511",
    "100000000000"                   => "4118054813",
    "1000000000000"                  => "37607912018",
    "10000000000000"                 => "346065536839",
    "100000000000000"                => "3204941750802",
    "1000000000000000"               => "29844570422669",
    "10000000000000000"              => "279238341033925",
    "100000000000000000"             => "2623557157654233",
    "1000000000000000000"            => "24739954287740860",
    "10000000000000000000"           => "234057667276344607",
    "100000000000000000000"          => "2220819602560918840",
    "1000000000000000000000"         => "21127269486018731928",
    "10000000000000000000000"        => "201467286689315906290",
    "100000000000000000000000"       => "1925320391606803968923",
    "1000000000000000000000000"      => "18435599767349200867866",
    "10000000000000000000000000"     => "176846309399143769411680",
    "100000000000000000000000000"    => "1699246750872437141327603",
    "1000000000000000000000000000"   => "16352460426841680446427399",
    "10000000000000000000000000000"  => "157589269275973410412739598",
    "100000000000000000000000000000" => "1520698109714272166094258063",

    # Floor(sqrt(10^(2n+1))) — OEIS A122121
    "3162277"                     => "227647",
    "31622776"                    => "1951957",
    "316227766"                   => "17082666",
    "3162277660"                  => "151876932",
    "31622776601"                 => "1367199811",
    "316227766016"                => "12431880460",
    "3162277660168"               => "113983535775",
    "31622776601683"              => "1052370166553",
    "316227766016837"             => "9773865306521",
    "3162277660168379"            => "91238789797384",
    "31622776601683793"           => "855502559228365",
    "316227766016837933"          => "8052994747583677",
    "3162277660168379331"         => "76066570954337300",
    "31622776601683793319"        => "720722641159301040",
    "316227766016837933199"       => "6847673381013822597",
    "3162277660168379331998"      => "65223071241820793398",
    "31622776601683793319988"     => "622647095301172021671",
    "316227766016837933199889"    => "5956317545928249075039",
    "3162277660168379331998893"   => "57086403558149290301868",
    "31622776601683793319988935"  => "548074549053620897173483",
    "316227766016837933199889354" => "5270353162790246525701178",

    # pi(n) for n = floor(10^k / m), various small m
    "10309278350515"   => "356392355629",
    "104166666666"     => "4282427463",
    "1041666666666"    => "39114743793",
    "10416666666666"   => "359975632063",
    "104166666666666"  => "3334113665385",
    "10638297872340"   => "367367036747",
    "106382978723404"  => "3402756647584",
    "1063829787234"    => "39915368200",
    "1111111111111111" => "33056584174789",
    "111111111111111"  => "3549047966156",
    "11111111111111"   => "383118399785",
    "1111111111111"    => "41621368073",
    "111111111111"     => "4555800188",
    "11235955056179"   => "387273732609",
    "116279069767441"  => "3708728266177",
    "1162790697674"    => "43483092670",
    "11627906976744"   => "400309533209",
    "12048192771084"   => "414271426137",
    "1219512195121"    => "45522996012",
    "12195121951219"   => "419148610121",
    "121951219512195"  => "3883734854095",
    "1234567901234"    => "46063874709",
    "12345679012345"   => "424144130506",
    "123456790123456"  => "3930144678942",
    "123456790123"     => "5040193472",
    "1250000000000000" => "37058666602970",
    "125000000000000"  => "3977696644164",
    "12500000000000"   => "429262473738",
    "1250000000000"    => "46618036665",
    "125000000000"     => "5100605440",
    "12658227848101"   => "434508183042",
    "133333333333333"  => "4234170485973",
    "13513513513513"   => "462826669200",
    "135135135135135"  => "4289558418919",
    "1351351351351"    => "50251513574",
    "13698630136986"   => "468947991808",
    "137174211248"     => "5576181164",
    "138888888888"     => "5643027425",
    "1388888888888"    => "51594745537",
    "13888888888888"   => "475236412836",
    "138888888888888"  => "4404877383892",
    "14084507042253"   => "481699024983",
    "14285714285"      => "639663667",
    "142857142857142"  => "4526682048776",
    "1428571428571428" => "42185591326481",
    "14285714285714"   => "488343188629",
    "1428571428571"    => "53013308951",
    "142857142857"     => "5797603975",
    "1428571428"       => "71341127",
    "14925373134328"   => "509445517621",
    "156250000000000"  => "4937023458275",
    "15625000000000"   => "532492027113",
    "1562500000000"    => "57790812036",
    "156250000000"     => "6318108621",
    "16129032258064"   => "549074258778",
    "161290322580645"  => "5091169161272",
    "1612903225806"    => "59584931448",
    "16393442622950"   => "557766346667",
    "1666666666666"    => "61496476037",
    "16666666666666"   => "566743254123",
    "166666666666666"  => "5255429125062",
    "1666666666666666" => "48993281778403",
    "166666666666"     => "6721737844",
    "16949152542372"   => "576019453837",
    "169491525423728"  => "5341670076170",
    "17241379310344"   => "585610147276",
    "172413793103448"  => "5430838640194",
    "1724137931034"    => "63537448178",
    "1851851851851"    => "68064427524",
    "185185185185185"  => "5820007824922",
    "18518518518518"   => "627463720453",
    "185185185185"     => "7436940214",
    "18867924528301"   => "638897445082",
    "188679245283018"  => "5926333832509",
    "195312500000"     => "7826876358",
    "2000000000000000" => "58478215681891",
    "200000000000000"  => "6270424651315",
    "20000000000000"   => "675895909271",
    "2000000000000"    => "73301896139",
    "200000000000"     => "8007105059",
    "20000000000"      => "882206716",
    "2000000000"       => "98222287",
    "208333333333333"  => "6523333943109",
    "20833333333333"   => "703087019180",
    "2083333333333"    => "76241905348",
    "208333333333"     => "8327097931",
    "21276595744680"   => "717535853858",
    "212765957446808"  => "6657734355710",
    "2127659574468"    => "77804020175",
    "217391304347826"  => "6797887995201",
    "21739130434782"   => "732602396419",
    "2173913043478"    => "79432841094",
    "222222222222222"  => "6944174236933",
    "22222222222222"   => "748327378590",
    "232558139534883"  => "7256837914986",
    "2325581395348"    => "84765434681",
    "23255813953488"   => "781934640950",
    "2439024390243"    => "88746041012",
    "243902439024390"  => "7599523082981",
    "24390243902439"   => "818764914659",
    "24414062500"      => "1067503007",
    "2500000000000000" => "72623478149504",
    "250000000000000"  => "7783516108362",
    "25000000000000"   => "838538039510",
    "2500000000000"    => "90882915772",
    "250000000000"     => "9920079604",
    "263157894736842"  => "8180096926854",
    "26315789473684"   => "881153649559",
    "2631578947368"    => "95487895305",
    "270270270270270"  => "8394214368915",
    "27027027027027"   => "904160213736",
    "2702702702702"    => "97973697682",
    "277777777777"     => "10976339421",
    "2777777777777"    => "100595137378",
    "277777777777777"  => "8620043221450",
    "27777777777777"   => "928423650342",
    "2941176470588"    => "106292129666",
    "294117647058823"  => "9110926447966",
    "29411764705882"   => "981160027955",
    "312500000000000"  => "9662193791231",
    "31250000000000"   => "1040375711845",
    "3125000000000"    => "112688110354",
    "312500000000"     => "12291069731",
    "3225806451612"    => "116189994198",
    "322580645161290"  => "9964085882898",
    "32258064516129"   => "1072800852668",
    "3333333333"       => "159687131",
    "33333333333"      => "1437873584",
    "333333333333"     => "13077229631",
    "3333333333333"    => "119921146470",
    "33333333333333"   => "1107351740716",
    "333333333333333"  => "10285792709504",
    "3333333333333333" => "96028033831165",
    "3448275862068"    => "123905016177",
    "34482758620689"   => "1144245941771",
    "344827586206896"  => "10629343624801",
    "370370370370"     => "14470326153",
    "3703703703703"    => "132741764431",
    "370370370370370"  => "11391582423328",
    "37037037037037"   => "1226093877740",
    "3846153846153"    => "137660753262",
    "38461538461538"   => "1271660515811",
    "384615384615384"  => "11815989555322",
    "390625000000"     => "15229927577",
    "400000000000000"  => "12273824155491",
    "40000000000000"   => "1320811971702",
    "4000000000000"    => "142966208126",
    "411522633744"     => "16012100612",
    "416666666666"     => "16204404651",
    "4166666666666"    => "148705830761",
    "41666666666666"   => "1373991431074",
    "416666666666666"  => "12769221416219",
    "434782608695"     => "16880968331",
    "4347826086956"    => "154935587256",
    "43478260869565"   => "1431718321829",
    "434782608695652"  => "13307030092080",
    "4545454545454"    => "161721584165",
    "45454545454545"   => "1494605849770",
    "454545454545454"  => "13892973435920",
    "48828125000"      => "2072060851",
    "5000000000000000" => "142377417196364",
    "500000000000000"  => "15237833654620",
    "50000000000000"   => "1638923764567",
    "5000000000000"    => "177291661649",
    "500000000000"     => "19308136142",
    "50000000000"      => "2119654578",
    "5000000000"       => "234954223",
    "5154639175257"    => "182577435554",
    "51546391752577"   => "1687924844082",
    "526315789473"     => "20284110437",
    "5263157894736"    => "186283514910",
    "52631578947368"   => "1722283675178",
    "526315789473684"  => "16014764115918",
    "555555555555"     => "21366409911",
    "5555555555555"    => "196256438549",
    "55555555555555"   => "1814751287143",
    "555555555555555"  => "16876678891443",
    "5617977528089"    => "198383139405",
    "56179775280898"   => "1834471308010",
    "588235294117"     => "22573541239",
    "5882352941176"    => "207381587155",
    "58823529411764"   => "1917915926717",
    "588235294117647"  => "17838418304439",
    "60240963855421"   => "1962605300107",
    "6024096385542"    => "212200329534",
    "625000000000000"  => "18918502148600",
    "62500000000000"   => "2033760752473",
    "6250000000000"    => "219872297126",
    "625000000000"     => "23928619968",
    "62500000000"      => "2624687920",
    "6329113924050"    => "222556880781",
    "63291139240506"   => "2058660902059",
    "66666666666"      => "2792083254",
    "666666666666"     => "25460842735",
    "6666666666666"    => "233998450404",
    "66666666666666"   => "2164793088936",
    "666666666666666"  => "20140348712061",
    "68493150684931"   => "2222149729012",
    "6849315068493"    => "240181159890",
    "7042253521126"    => "246706152157",
    "70422535211267"   => "2282685767114",
    "71428571428571"   => "2314230232867",
    "714285714285714"  => "21534014686327",
    "7142857142857"    => "250106086224",
    "74626865671641"   => "2414421726909",
    "7462686567164"    => "260904088230",
    "769230769230"     => "29217952804",
    "7692307692307"    => "268646892583",
    "76923076923076"   => "2486270966954",
    "769230769230769"  => "23138727712057",
    "76923076923"      => "3202406654",
    "7692307692"       => "354277612",
    "769230769"        => "39643338",
    "781250000000"     => "29656993786",
    "8196721311475"    => "285628623364",
    "81967213114754"   => "2643871089132",
    "833333333333"     => "31556671177",
    "8333333333333"    => "290221685283",
    "83333333333333"   => "2686501525128",
    "833333333333333"  => "25006683974403",
    "83333333333"      => "3457724375",
    "8474576271186"    => "294967792369",
    "84745762711864"   => "2730554048517",
    "9090909090"       => "415488843",
    "909090909090"     => "34312203318",
    "90909090909090"   => "2922521280710",
    "909090909090909"  => "27208902168103",
    "9090909090909"    => "315647597350",
    "90909090909"      => "3758465612",
    "909090909"        => "46450197",
    "9174311926605"    => "318442343729",
    "9345794392523"    => "324185856798",
    "9433962264150"    => "327137540745",
    "94339622641509"   => "3029193708578",
    "9708737864077"    => "336330446255",
    "97656250000"      => "4025476060",
    "9900990099009"    => "342757310840",

    # Other prime counts
    '999999999999989' => '29844570422669',
    '99999999999973'  => '3204941750802',
    '9999999999971'   => '346065536839',
    '2760727302517'   => '100000000000',
    '2474799787573'   => '90000000000',
    '2190026988349'   => '80000000000',
    '1906555030411'   => '70000000000',
    '1624571841097'   => '60000000000',
    '1344326694119'   => '50000000000',
    '1066173339601'   => '40000000000',
    '999999999989'    => '37607912018',
    '928037044463'    => '35000000000',
    '790645490053'    => '30000000000',
    '654124187867'    => '25000000000',
    '518649879439'    => '20000000000',
    '384489816343'    => '15000000000',
    '252097800623'    => '10000000000',
    '225898512559'    => '9000000000',
    '200000000507'    => '8007105083',
    '173862636221'    => '7000000000',
    '148059109201'    => '6000000000',
    '122430513841'    => '5000000000',
    '99999999977'     => '4118054813',
    '97011687217'     => '4000000000',
    '71856445751'     => '3000000000',
    '49999999967'     => '2119654578',
    '47055833459'     => '2000000000',
    '44999999971'     => '1916268743',
    '39999999979'     => '1711955433',
    '34999999999'     => '1506589876',
    '29999999993'     => '1300005926',
    '24999999991'     => '1091987405',
    '22801763489'     => '1000000000',
    '21999999977'     => '966358351',
    '20999999981'     => '924324489',
    '20422213579'     => '900000000',
    '19999999967'     => '882206716',
    '18999999959'     => '840000027',
    '18054236957'     => '800000000',
    '17999999989'     => '797703398',
    '16999999999'     => '755305935',
    '15999999991'     => '712799821',
    '15699342107'     => '700000000',
    '14999999969'     => '670180516',
    '13999999991'     => '627440336',
    '13359555403'     => '600000000',
    '12999999959'     => '584570200',
    '11999999983'     => '541555851',
    '11037271757'     => '500000000',
    '10999999999'     => '498388617',
    '9999999967'      => '455052511',
    '9899999987'      => '450708777',
    '9883692017'      => '450000000',
    '9699999997'      => '442014876',
    '9499999979'      => '433311792',
    '9299999999'      => '424603409',
    '8999999993'      => '411523195',
    '8736028057'      => '400000000',
    '8699999953'      => '398425675',
    '8499999941'      => '389682427',
    '8299999993'      => '380930729',
    '7999999957'      => '367783654',
    '7594955549'      => '350000000',
    '7499999999'      => '345826612',
    '7299999979'      => '337024801',
    '6999999989'      => '323804352',
    '6699999991'      => '310558733',
    '6499999993'      => '301711468',
    '6461335109'      => '300000000',
    '6399999959'      => '297285198',
    '6299999977'      => '292856421',
    '5999999989'      => '279545368',
    '5699999999'      => '266206294',
    '5499999997'      => '257294520',
    '5336500537'      => '250000000',
    '5299999967'      => '248370960',
    '5199999977'      => '243902342',
    '4999999937'      => '234954223',
    '4899999983'      => '230475545',
    '4699999987'      => '221504167',
    '4499999989'      => '212514323',
    '4299999973'      => '203507248',
    '4222234741'      => '200000000',
    '4199999929'      => '198996103',
    '4000000483'      => '189961831',
    '3999999979'      => '189961812',
    '3899999989'      => '185436625',
    '3799999979'      => '180906194',
    '3699999991'      => '176369517',
    '3499999991'      => '167279333',
    '3399999971'      => '162725196',
    '3299999959'      => '158165829',
    '3099999953'      => '149028641',
    '2999999929'      => '144449537',
    '2899999957'      => '139864011',
    '2799999973'      => '135270258',
    '2699999989'      => '130670192',
    '2599999991'      => '126062167',
    '2499999977'      => '121443371',
    '2399999983'      => '116818447',
    '2199999973'      => '107540122',
    '2038074743'      => '100000000',
    '1999999973'      => '98222287',
    '1899999979'      => '93547928',
    '1799999977'      => '88862422',
    '1699999997'      => '84163019',
    '1599999983'      => '79451833',
    '1499999957'      => '74726528',
    '1399999987'      => '69985473',
    '1299999983'      => '65228333',
    '1199999993'      => '60454705',
    '1099999997'      => '55662470',
    '999999937'       => '50847534',
    '982451653'       => '50000000',
    '949999993'       => '48431471',
    '899999963'       => '46009215',
    '849999977'       => '43581966',
    '799999999'       => '41146179',
    '749999989'       => '38703181',
    '699999953'       => '36252931',
    '649999993'       => '33793395',
    '599999971'       => '31324703',
    '549999959'       => '28845356',
    '499999993'       => '26355867',
    '449999993'       => '23853038',
    '399999959'       => '21336326',
    '373587883'       => '20000000',
    '369999979'       => '19818405',
    '359999989'       => '19311288',
    '349999999'       => '18803526',
    '329999987'       => '17785475',
    '299999977'       => '16252325',
    '289999999'       => '15739663',
    '269999993'       => '14711384',
    '249999991'       => '13679318',
    '229999981'       => '12642573',
    '199999991'       => '11078937',
    '189999989'       => '10555473',
    '179424673'       => '10000000',
    '169999967'       => '9503083',
    '159999997'       => '8974458',
    '149999957'       => '8444396',
    '139999991'       => '7912199',
    '119999987'       => '6841648',
    '99999989'        => '5761455',
    '94999951'        => '5489749',
    '89999999'        => '5216954',
    '86028121'        => '5000000',
    '84999979'        => '4943731',
    '79999987'        => '4669382',
    '74999959'        => '4394304',
    '69999989'        => '4118064',
    '64999981'        => '3840554',
    '59999999'        => '3562115',
    '54999943'        => '3282200',
    '49999991'        => '3001134',
    '44999971'        => '2718160',
    '39999983'        => '2433654',
    '34999969'        => '2146775',
    '32452843'        => '2000000',
    '29999999'        => '1857859',
    '24999983'        => '1565927',
    '19999999'        => '1270607',
    '18999997'        => '1211050',
    '17999987'        => '1151367',
    '16999999'        => '1091314',
    '15999989'        => '1031130',
    '15485863'        => '1000000',
    '14999981'        => '970704',
    '13999981'        => '910077',
    '12999997'        => '849252',
    '11999989'        => '788060',
    '10999997'        => '726517',
    '9999991'         => '664579',
    '8999993'         => '602489',
    '7999993'         => '539777',
    '7368787'         => '500000',
    '6999997'         => '476648',
    '5999993'         => '412849',
    '4999999'         => '348513',
    '3999971'         => '283146',
    '3499999'         => '250150',
    '2999999'         => '216816',
    '2750159'         => '200000',
    '2499997'         => '183072',
    '1999993'         => '148933',
    '1299709'         => '100000',
    '1159523'         => '90000',
    '1020379'         => '80000',

    # Powers of 2 — OEIS A007053
    "1048576"                      => "82025",
    "2097152"                      => "155611",
    "4194304"                      => "295947",
    "8388608"                      => "564163",
    "16777216"                     => "1077871",
    "33554432"                     => "2063689",
    "67108864"                     => "3957809",
    "134217728"                    => "7603553",
    "268435456"                    => "14630843",
    "536870912"                    => "28192750",
    "1073741824"                   => "54400028",
    "2147483648"                   => "105097565",
    "4294967296"                   => "203280221",
    "8589934592"                   => "393615806",
    "17179869184"                  => "762939111",
    "34359738368"                  => "1480206279",
    "68719476736"                  => "2874398515",
    "137438953472"                 => "5586502348",
    "274877906944"                 => "10866266172",
    "549755813888"                 => "21151907950",
    "1099511627776"                => "41203088796",
    "2199023255552"                => "80316571436",
    "4398046511104"                => "156661034233",
    "8796093022208"                => "305761713237",
    "17592186044416"               => "597116381732",
    "35184372088832"               => "1166746786182",
    "70368744177664"               => "2280998753949",
    "140737488355328"              => "4461632979717",
    "281474976710656"              => "8731188863470",
    "562949953421312"              => "17094432576778",
    "1125899906842624"             => "33483379603407",
    "2251799813685248"             => "65612899915304",
    "4503599627370496"             => "128625503610475",
    "9007199254740992"             => "252252704148404",
    "18014398509481984"            => "494890204904784",
    "36028797018963968"            => "971269945245201",
    "72057594037927936"            => "1906879381028850",
    "144115188075855872"           => "3745011184713964",
    "288230376151711744"           => "7357400267843990",
    "576460752303423488"           => "14458792895301660",
    "1152921504606846976"          => "28423094496953330",
    "2305843009213693952"          => "55890484045084135",
    "4611686018427387904"          => "109932807585469973",
    "9223372036854775808"          => "216289611853439384",
    "18446744073709551616"         => "425656284035217743",
    "36893488147419103232"         => "837903145466607212",
    "73786976294838206464"         => "1649819700464785589",
    "147573952589676412928"        => "3249254387052557215",
    "295147905179352825856"        => "6400771597544937806",
    "590295810358705651712"        => "12611864618760352880",
    "1180591620717411303424"       => "24855455363362685793",
    "2361183241434822606848"       => "48995571600129458363",
    "4722366482869645213696"       => "96601075195075186855",
    "9444732965739290427392"       => "190499823401327905601",
    "18889465931478580854784"      => "375744164937699609596",
    "37778931862957161709568"      => "741263521140740113483",
    "75557863725914323419136"      => "1462626667154509638735",
    "151115727451828646838272"     => "2886507381056867953916",
    "302231454903657293676544"     => "5697549648954257752872",
    "604462909807314587353088"     => "11248065615133675809379",
    "1208925819614629174706176"    => "22209558889635384205844",
    "2417851639229258349412352"    => "43860397052947409356492",
    "4835703278458516698824704"    => "86631124695994360074872",
    "9671406556917033397649408"    => "171136408646923240987028",
    "19342813113834066795298816"   => "338124238545210097236684",
    "38685626227668133590597632"   => "668150111666935905701562",
    "77371252455336267181195264"   => "1320486952377516565496055",
    "154742504910672534362390528"  => "2610087356951889016077639",
    "309485009821345068724781056"  => "5159830247726102115466054",
    "618970019642690137449562112"  => "10201730804263125133012340",
    "1237940039285380274899124224" => "20172933541156002700963336",
    "2475880078570760549798248448" => "39895115987049029184882256",
    "4951760157141521099596496896" => "78908656317357166866404346",
                  );

{
    no strict 'refs';

    *{__PACKAGE__ . '::' . '/'}   = \&div;
    *{__PACKAGE__ . '::' . '÷'}   = \&div;
    *{__PACKAGE__ . '::' . '*'}   = \&mul;
    *{__PACKAGE__ . '::' . '+'}   = \&add;
    *{__PACKAGE__ . '::' . '-'}   = \&sub;
    *{__PACKAGE__ . '::' . '%'}   = \&mod;
    *{__PACKAGE__ . '::' . '**'}  = \&pow;
    *{__PACKAGE__ . '::' . '++'}  = \&inc;
    *{__PACKAGE__ . '::' . '--'}  = \&dec;
    *{__PACKAGE__ . '::' . '<'}   = \&lt;
    *{__PACKAGE__ . '::' . '>'}   = \&gt;
    *{__PACKAGE__ . '::' . '&'}   = \&and;
    *{__PACKAGE__ . '::' . '|'}   = \&or;
    *{__PACKAGE__ . '::' . '^'}   = \&xor;
    *{__PACKAGE__ . '::' . '<=>'} = \&cmp;
    *{__PACKAGE__ . '::' . '<='}  = \&le;
    *{__PACKAGE__ . '::' . '≤'}   = \&le;
    *{__PACKAGE__ . '::' . '>='}  = \&ge;
    *{__PACKAGE__ . '::' . '≥'}   = \&ge;
    *{__PACKAGE__ . '::' . '=='}  = \&eq;
    *{__PACKAGE__ . '::' . '!='}  = \&ne;
    *{__PACKAGE__ . '::' . '≠'}   = \&ne;
    *{__PACKAGE__ . '::' . '..'}  = \&to;
    *{__PACKAGE__ . '::' . '..^'} = \&xto;
    *{__PACKAGE__ . '::' . '^..'} = \&xdownto;
    *{__PACKAGE__ . '::' . '!'}   = \&factorial;
    *{__PACKAGE__ . '::' . '!!'}  = \&double_factorial;
    *{__PACKAGE__ . '::' . '%%'}  = \&is_div;
    *{__PACKAGE__ . '::' . '>>'}  = \&shift_right;
    *{__PACKAGE__ . '::' . '<<'}  = \&shift_left;
    *{__PACKAGE__ . '::' . '~'}   = \&not;
    *{__PACKAGE__ . '::' . ':'}   = \&pair;
    *{__PACKAGE__ . '::' . '//'}  = \&idiv;
    *{__PACKAGE__ . '::' . '=~='} = \&approx_eq;
    *{__PACKAGE__ . '::' . '≅'}   = \&approx_eq;
    *{__PACKAGE__ . '::' . '<~>'} = \&approx_cmp;
};

1
