—package
Math::BigInt::Calc;
use
5.006001;
use
strict;
use
warnings;
use
Math::BigInt::Lib;
our
$VERSION
=
'2.005002'
;
$VERSION
=~
tr
/_//d;
our
@ISA
= (
'Math::BigInt::Lib'
);
# Package to store unsigned big integers in decimal and do math with them
#
# Internally the numbers are stored in an array with at least 1 element, no
# leading zero parts (except the first) and in base 1eX where X is determined
# automatically at loading time to be the maximum possible value
#
# todo:
# - fully remove funky $# stuff in div() (maybe - that code scares me...)
##############################################################################
# global constants, flags and accessory
# constants for easier life
my
$MAX_EXP_F
;
# the maximum possible base 10 exponent with "no integer"
my
$MAX_EXP_I
;
# the maximum possible base 10 exponent with "use integer"
my
$MAX_BITS
;
# the maximum possible number of bits for $AND_BITS etc.
my
$BASE_LEN
;
# the current base exponent in use
my
$USE_INT
;
# whether "use integer" is used in the computations
my
$BASE
;
# the current base, e.g., 10000 if $BASE_LEN is 5
my
$MAX_VAL
;
# maximum value for an element, i.e., $BASE - 1
my
$AND_BITS
;
# maximum value used in binary and, e.g., 0xffff
my
$OR_BITS
;
# ditto for binary or
my
$XOR_BITS
;
# ditto for binary xor
my
$AND_MASK
;
# $AND_BITS + 1, e.g., 0x10000 if $AND_BITS is 0xffff
my
$OR_MASK
;
# ditto for binary or
my
$XOR_MASK
;
# ditto for binary xor
sub
config {
my
$self
=
shift
;
croak
"Missing input argument"
unless
@_
;
# Called as a getter.
if
(
@_
== 1) {
my
$param
=
shift
;
croak
"Parameter name must be a non-empty string"
unless
defined
$param
&&
length
$param
;
return
$BASE_LEN
if
$param
eq
'base_len'
;
return
$USE_INT
if
$param
eq
'use_int'
;
croak
"Unknown parameter '$param'"
;
}
# Called as a setter.
my
$opts
;
while
(
@_
) {
my
$param
=
shift
;
croak
"Parameter name must be a non-empty string"
unless
defined
$param
&&
length
$param
;
croak
"Missing value for parameter '$param'"
unless
@_
;
my
$value
=
shift
;
if
(
$param
eq
'base_len'
||
$param
eq
'use_int'
) {
$opts
-> {
$param
} =
$value
;
next
;
}
croak
"Unknown parameter '$param'"
;
}
$BASE_LEN
=
$opts
-> {base_len}
if
exists
$opts
-> {base_len};
$USE_INT
=
$opts
-> {use_int}
if
exists
$opts
-> {use_int};
__PACKAGE__ -> _base_len(
$BASE_LEN
,
$USE_INT
);
return
$self
;
}
sub
_base_len {
#my $class = shift; # $class is not used
shift
;
if
(
@_
) {
# if called as setter ...
my
(
$base_len
,
$use_int
) =
@_
;
croak
"The base length must be a positive integer"
unless
defined
(
$base_len
) &&
$base_len
==
int
(
$base_len
)
&&
$base_len
> 0;
if
(
$use_int
&& (
$base_len
>
$MAX_EXP_I
) ||
!
$use_int
&& (
$base_len
>
$MAX_EXP_F
))
{
croak
"The maximum base length (exponent) is $MAX_EXP_I with"
,
" 'use integer' and $MAX_EXP_F without 'use integer'. The"
,
" requested settings, a base length of $base_len "
,
$use_int
?
"with"
:
"without"
,
" 'use integer', is invalid."
;
}
$BASE_LEN
=
$base_len
;
$BASE
= 0 + (
"1"
. (
"0"
x
$BASE_LEN
));
$MAX_VAL
=
$BASE
- 1;
$USE_INT
=
$use_int
? 1 : 0;
{
no
warnings
"redefine"
;
if
(
$use_int
) {
*_mul
= \
&_mul_use_int
;
*_div
= \
&_div_use_int
;
}
else
{
*_mul
= \
&_mul_no_int
;
*_div
= \
&_div_no_int
;
}
}
}
# Find max bits. This is the largest power of two that is both no larger
# than $BASE and no larger than the maximum integer (i.e., ~0). We need
# this limitation because _and(), _or(), and _xor() only work on one
# element at a time.
my
$umax
= ~0;
# largest unsigned integer
my
$tmp
=
$umax
<
$BASE
?
$umax
:
$BASE
;
$MAX_BITS
= 0;
while
(
$tmp
>>= 1) {
$MAX_BITS
++;
}
# Limit to 32 bits for portability. Is this really necessary? XXX
$MAX_BITS
= 32
if
$MAX_BITS
> 32;
# Find out how many bits _and, _or and _xor can take (old default = 16).
# Are these tests really necessary? Can't we just use $MAX_BITS? XXX
for
(
$AND_BITS
=
$MAX_BITS
;
$AND_BITS
> 0 ;
$AND_BITS
--) {
my
$x
= CORE::
oct
(
'0b'
.
'1'
x
$AND_BITS
);
my
$y
=
$x
&
$x
;
my
$z
= 2 * (2 ** (
$AND_BITS
- 1)) + 1;
last
unless
$AND_BITS
<
$MAX_BITS
&&
$x
==
$z
&&
$y
==
$x
;
}
for
(
$XOR_BITS
=
$MAX_BITS
;
$XOR_BITS
> 0 ;
$XOR_BITS
--) {
my
$x
= CORE::
oct
(
'0b'
.
'1'
x
$XOR_BITS
);
my
$y
=
$x
^
$x
;
my
$z
= 2 * (2 ** (
$XOR_BITS
- 1)) + 1;
last
unless
$XOR_BITS
<
$MAX_BITS
&&
$x
==
$z
&&
$y
==
$x
;
}
for
(
$OR_BITS
=
$MAX_BITS
;
$OR_BITS
> 0 ;
$OR_BITS
--) {
my
$x
= CORE::
oct
(
'0b'
.
'1'
x
$OR_BITS
);
my
$y
=
$x
|
$x
;
my
$z
= 2 * (2 ** (
$OR_BITS
- 1)) + 1;
last
unless
$OR_BITS
<
$MAX_BITS
&&
$x
==
$z
&&
$y
==
$x
;
}
$AND_MASK
= __PACKAGE__->_new(( 2 **
$AND_BITS
));
$XOR_MASK
= __PACKAGE__->_new(( 2 **
$XOR_BITS
));
$OR_MASK
= __PACKAGE__->_new(( 2 **
$OR_BITS
));
return
$BASE_LEN
unless
wantarray
;
return
(
$BASE_LEN
,
$BASE
,
$AND_BITS
,
$XOR_BITS
,
$OR_BITS
,
$BASE_LEN
,
$MAX_VAL
,
$MAX_BITS
,
$MAX_EXP_F
,
$MAX_EXP_I
,
$USE_INT
);
}
sub
_new {
# Given a string representing an integer, returns a reference to an array
# of integers, where each integer represents a chunk of the original input
# integer.
my
(
$class
,
$str
) =
@_
;
#unless ($str =~ /^([1-9]\d*|0)\z/) {
# croak("Invalid input string '$str'");
#}
my
$input_len
=
length
(
$str
) - 1;
# Shortcut for small numbers.
return
bless
[
$str
],
$class
if
$input_len
<
$BASE_LEN
;
my
$format
=
"a"
. ((
$input_len
%
$BASE_LEN
) + 1);
$format
.= $] < 5.008 ?
"a$BASE_LEN"
x
int
(
$input_len
/
$BASE_LEN
)
:
"(a$BASE_LEN)*"
;
my
$self
= [
reverse
(
map
{ 0 +
$_
}
unpack
(
$format
,
$str
)) ];
return
bless
$self
,
$class
;
}
BEGIN {
# Compute $MAX_EXP_F, the maximum usable base 10 exponent.
# The largest element in base 10**$BASE_LEN is 10**$BASE_LEN-1. For instance,
# with $BASE_LEN = 5, the largest element is 99_999, and the largest carry is
#
# int( 99_999 * 99_999 / 100_000 ) = 99_998
#
# so make sure that 99_999 * 99_999 + 99_998 is within the range of integers
# that can be represented accuratly.
#
# Note that on some systems with quadmath support, the following is within
# the range of numbers that can be represented exactly, but it still gives
# the incorrect value $r = 2 (even though POSIX::fmod($x, $y) gives the
# correct value of 1:
#
# $x = 99999999999999999;
# $y = 100000000000000000;
# $r = $x * $x % $y; # should be 1
#
# so also check for this.
for
(
$MAX_EXP_F
= 1 ; ;
$MAX_EXP_F
++) {
# when $MAX_EXP_F = 5
my
$MAX_EXP_FM1
=
$MAX_EXP_F
- 1;
# = 4
my
$bs
=
"1"
. (
"0"
x
$MAX_EXP_F
);
# = "100000"
my
$xs
=
"9"
x
$MAX_EXP_F
;
# = "99999"
my
$cs
= (
"9"
x
$MAX_EXP_FM1
) .
"8"
;
# = "99998"
my
$ys
=
$cs
. (
"0"
x
$MAX_EXP_FM1
) .
"1"
;
# = "9999800001"
# Compute and check the product.
my
$yn
=
$xs
*
$xs
;
# = 9999800001
last
if
$yn
!=
$ys
;
# Compute and check the remainder.
my
$rn
=
$yn
%
$bs
;
# = 1
last
if
$rn
!= 1;
# Compute and check the carry. The division here is exact.
my
$cn
= (
$yn
-
$rn
) /
$bs
;
# = 99998
last
if
$cn
!=
$cs
;
# Compute and check product plus carry.
my
$zs
=
$cs
. (
"9"
x
$MAX_EXP_F
);
# = "9999899999"
my
$zn
=
$yn
+
$cn
;
# = 99998999999
last
if
$zn
!=
$zs
;
last
if
$zn
- (
$zn
- 1) != 1;
}
$MAX_EXP_F
--;
# last test failed, so retract one step
# Compute $MAX_EXP_I, the maximum usable base 10 exponent within the range
# of what is available with "use integer". On older versions of Perl,
# integers are converted to floating point numbers, even though they are
# within the range of what can be represented as integers. For example, on
# some 64 bit Perls, 999999999 * 999999999 becomes 999999998000000000, not
# 999999998000000001, even though the latter is less than the maximum value
# for a 64 bit integer, 18446744073709551615.
my
$umax
= ~0;
# largest unsigned integer
for
(
$MAX_EXP_I
=
int
(0.5 *
log
(
$umax
) /
log
(10));
$MAX_EXP_I
> 0;
$MAX_EXP_I
--)
{
# when $MAX_EXP_I = 5
my
$MAX_EXP_IM1
=
$MAX_EXP_I
- 1;
# = 4
my
$bs
=
"1"
. (
"0"
x
$MAX_EXP_I
);
# = "100000"
my
$xs
=
"9"
x
$MAX_EXP_I
;
# = "99999"
my
$cs
= (
"9"
x
$MAX_EXP_IM1
) .
"8"
;
# = "99998"
my
$ys
=
$cs
. (
"0"
x
$MAX_EXP_IM1
) .
"1"
;
# = "9999800001"
# Compute and check the product.
my
$yn
=
$xs
*
$xs
;
# = 9999800001
next
if
$yn
!=
$ys
;
# Compute and check the remainder.
my
$rn
=
$yn
%
$bs
;
# = 1
next
if
$rn
!= 1;
# Compute and check the carry. The division here is exact.
my
$cn
= (
$yn
-
$rn
) /
$bs
;
# = 99998
next
if
$cn
!=
$cs
;
# Compute and check product plus carry.
my
$zs
=
$cs
. (
"9"
x
$MAX_EXP_I
);
# = "9999899999"
my
$zn
=
$yn
+
$cn
;
# = 99998999999
next
if
$zn
!=
$zs
;
next
if
$zn
- (
$zn
- 1) != 1;
last
;
}
(
$BASE_LEN
,
$USE_INT
) =
$MAX_EXP_F
>
$MAX_EXP_I
? (
$MAX_EXP_F
, 0) : (
$MAX_EXP_I
, 1);
__PACKAGE__ -> _base_len(
$BASE_LEN
,
$USE_INT
);
}
###############################################################################
sub
_zero {
# create a zero
my
$class
=
shift
;
return
bless
[ 0 ],
$class
;
}
sub
_one {
# create a one
my
$class
=
shift
;
return
bless
[ 1 ],
$class
;
}
sub
_two {
# create a two
my
$class
=
shift
;
return
bless
[ 2 ],
$class
;
}
sub
_ten {
# create a 10
my
$class
=
shift
;
my
$self
=
$BASE_LEN
== 1 ? [ 0, 1 ] : [ 10 ];
bless
$self
,
$class
;
}
sub
_1ex {
# create a 1Ex
my
$class
=
shift
;
my
$rem
=
$_
[0] %
$BASE_LEN
;
# remainder
my
$div
= (
$_
[0] -
$rem
) /
$BASE_LEN
;
# parts
# With a $BASE_LEN of 6, 1e14 becomes
# [ 000000, 000000, 100 ] -> [ 0, 0, 100 ]
bless
[ (0) x
$div
, 0 + (
"1"
. (
"0"
x
$rem
)) ],
$class
;
}
sub
_copy {
# make a true copy
my
$class
=
shift
;
return
bless
[ @{
$_
[0] } ],
$class
;
}
sub
import
{
my
$self
=
shift
;
my
$opts
;
my
(
$base_len
,
$use_int
);
while
(
@_
) {
my
$param
=
shift
;
croak
"Parameter name must be a non-empty string"
unless
defined
$param
&&
length
$param
;
croak
"Missing value for parameter '$param'"
unless
@_
;
my
$value
=
shift
;
if
(
$param
eq
'base_len'
||
$param
eq
'use_int'
) {
$opts
-> {
$param
} =
$value
;
next
;
}
croak
"Unknown parameter '$param'"
;
}
$base_len
=
exists
$opts
-> {base_len} ?
$opts
-> {base_len} :
$BASE_LEN
;
$use_int
=
exists
$opts
-> {use_int} ?
$opts
-> {use_int} :
$USE_INT
;
__PACKAGE__ -> _base_len(
$base_len
,
$use_int
);
return
$self
;
}
##############################################################################
# convert back to string and number
sub
_str {
# Convert number from internal base 1eN format to string format. Internal
# format is always normalized, i.e., no leading zeros.
my
$ary
=
$_
[1];
my
$idx
=
$#$ary
; #
index
of
last
element
if
(
$idx
< 0) {
# should not happen
croak(
"$_[1] has no elements"
);
}
# Handle first one differently, since it should not have any leading zeros.
my
$ret
=
int
(
$ary
->[
$idx
]);
if
(
$idx
> 0) {
# Interestingly, the pre-padd method uses more time.
# The old grep variant takes longer (14 vs. 10 sec).
my
$z
=
'0'
x (
$BASE_LEN
- 1);
while
(--
$idx
>= 0) {
$ret
.=
substr
(
$z
.
$ary
->[
$idx
], -
$BASE_LEN
);
}
}
$ret
;
}
sub
_num {
# Make a Perl scalar number (int/float) from a BigInt object.
my
$x
=
$_
[1];
return
$x
->[0]
if
@$x
== 1;
# below $BASE
# Start with the most significant element and work towards the least
# significant element. Avoid multiplying "inf" (which happens if the number
# overflows) with "0" (if there are zero elements in $x) since this gives
# "nan" which propagates to the output.
my
$num
= 0;
for
(
my
$i
=
$#$x
;
$i
>= 0 ; --
$i
) {
$num
*=
$BASE
;
$num
+=
$x
-> [
$i
];
}
return
$num
;
}
##############################################################################
# actual math code
sub
_add {
# (ref to int_num_array, ref to int_num_array)
#
# Routine to add two base 1eX numbers stolen from Knuth Vol 2 Algorithm A
# pg 231. There are separate routines to add and sub as per Knuth pg 233.
# This routine modifies array x, but not y.
my
(
$c
,
$x
,
$y
) =
@_
;
# $x + 0 => $x
return
$x
if
@$y
== 1 &&
$y
->[0] == 0;
# 0 + $y => $y->copy
if
(
@$x
== 1 &&
$x
->[0] == 0) {
@$x
=
@$y
;
return
$x
;
}
# For each in Y, add Y to X and carry. If after that, something is left in
# X, foreach in X add carry to X and then return X, carry. Trades one
# "$j++" for having to shift arrays.
my
$car
= 0;
my
$j
= 0;
for
my
$i
(
@$y
) {
$x
->[
$j
] -=
$BASE
if
$car
= ((
$x
->[
$j
] +=
$i
+
$car
) >=
$BASE
) ? 1 : 0;
$j
++;
}
while
(
$car
!= 0) {
$x
->[
$j
] -=
$BASE
if
$car
= ((
$x
->[
$j
] +=
$car
) >=
$BASE
) ? 1 : 0;
$j
++;
}
$x
;
}
sub
_inc {
# (ref to int_num_array, ref to int_num_array)
# Add 1 to $x, modify $x in place
my
(
$c
,
$x
) =
@_
;
for
my
$i
(
@$x
) {
return
$x
if
(
$i
+= 1) <
$BASE
;
# early out
$i
= 0;
# overflow, next
}
push
@$x
, 1
if
$x
->[-1] == 0;
# last overflowed, so extend
$x
;
}
sub
_dec {
# (ref to int_num_array, ref to int_num_array)
# Sub 1 from $x, modify $x in place
my
(
$c
,
$x
) =
@_
;
my
$MAX
=
$BASE
- 1;
# since MAX_VAL based on BASE
for
my
$i
(
@$x
) {
last
if
(
$i
-= 1) >= 0;
# early out
$i
=
$MAX
;
# underflow, next
}
pop
@$x
if
$x
->[-1] == 0 &&
@$x
> 1;
# last underflowed (but leave 0)
$x
;
}
sub
_sub {
# (ref to int_num_array, ref to int_num_array, swap)
#
# Subtract base 1eX numbers -- stolen from Knuth Vol 2 pg 232, $x > $y
# subtract Y from X by modifying x in place
my
(
$c
,
$sx
,
$sy
,
$s
) =
@_
;
my
$car
= 0;
my
$j
= 0;
if
(!
$s
) {
for
my
$i
(
@$sx
) {
last
unless
defined
$sy
->[
$j
] ||
$car
;
$i
+=
$BASE
if
$car
= ((
$i
-= (
$sy
->[
$j
] || 0) +
$car
) < 0);
$j
++;
}
# might leave leading zeros, so fix that
return
__strip_zeros(
$sx
);
}
for
my
$i
(
@$sx
) {
# We can't do an early out if $x < $y, since we need to copy the high
# chunks from $y. Found by Bob Mathews.
#last unless defined $sy->[$j] || $car;
$sy
->[
$j
] +=
$BASE
if
$car
= (
$sy
->[
$j
] =
$i
- (
$sy
->[
$j
] || 0) -
$car
) < 0;
$j
++;
}
# might leave leading zeros, so fix that
__strip_zeros(
$sy
);
}
sub
_mul_use_int {
# (ref to int_num_array, ref to int_num_array)
# multiply two numbers in internal representation
# modifies first arg, second need not be different from first
# works for 64 bit integer with "use integer"
my
(
$c
,
$xv
,
$yv
) =
@_
;
if
(
@$yv
== 1) {
# shortcut for two very short numbers (improved by Nathan Zook) works
# also if xv and yv are the same reference, and handles also $x == 0
if
(
@$xv
== 1) {
if
((
$xv
->[0] *=
$yv
->[0]) >=
$BASE
) {
$xv
->[0] =
$xv
->[0] - (
$xv
->[1] =
$xv
->[0] /
$BASE
) *
$BASE
;
}
return
$xv
;
}
# $x * 0 => 0
if
(
$yv
->[0] == 0) {
@$xv
= (0);
return
$xv
;
}
# multiply a large number a by a single element one, so speed up
my
$y
=
$yv
->[0];
my
$car
= 0;
foreach
my
$i
(
@$xv
) {
#$i = $i * $y + $car; $car = $i / $BASE; $i -= $car * $BASE;
$i
=
$i
*
$y
+
$car
;
$i
-= (
$car
=
$i
/
$BASE
) *
$BASE
;
}
push
@$xv
,
$car
if
$car
!= 0;
return
$xv
;
}
# shortcut for result $x == 0 => result = 0
return
$xv
if
@$xv
== 1 &&
$xv
->[0] == 0;
# since multiplying $x with $x fails, make copy in this case
$yv
=
$c
->_copy(
$xv
)
if
$xv
==
$yv
;
# same references?
my
@prod
= ();
my
(
$prod
,
$car
,
$cty
);
for
my
$xi
(
@$xv
) {
$car
= 0;
$cty
= 0;
# looping through this if $xi == 0 is silly - so optimize it away!
$xi
= (
shift
(
@prod
) || 0),
next
if
$xi
== 0;
for
my
$yi
(
@$yv
) {
$prod
=
$xi
*
$yi
+ (
$prod
[
$cty
] || 0) +
$car
;
$prod
[
$cty
++] =
$prod
- (
$car
=
$prod
/
$BASE
) *
$BASE
;
}
$prod
[
$cty
] +=
$car
if
$car
;
# need really to check for 0?
$xi
=
shift
(
@prod
) || 0;
# || 0 makes v5.005_3 happy
}
push
@$xv
,
@prod
;
$xv
;
}
sub
_mul_no_int {
# (ref to int_num_array, ref to int_num_array)
# multiply two numbers in internal representation
# modifies first arg, second need not be different from first
my
(
$c
,
$xv
,
$yv
) =
@_
;
if
(
@$yv
== 1) {
# shortcut for two very short numbers (improved by Nathan Zook) works
# also if xv and yv are the same reference, and handles also $x == 0
if
(
@$xv
== 1) {
if
((
$xv
->[0] *=
$yv
->[0]) >=
$BASE
) {
my
$rem
=
$xv
->[0] %
$BASE
;
$xv
->[1] = (
$xv
->[0] -
$rem
) /
$BASE
;
$xv
->[0] =
$rem
;
}
return
$xv
;
}
# $x * 0 => 0
if
(
$yv
->[0] == 0) {
@$xv
= (0);
return
$xv
;
}
# multiply a large number a by a single element one, so speed up
my
$y
=
$yv
->[0];
my
$car
= 0;
my
$rem
;
foreach
my
$i
(
@$xv
) {
$i
=
$i
*
$y
+
$car
;
$rem
=
$i
%
$BASE
;
$car
= (
$i
-
$rem
) /
$BASE
;
$i
=
$rem
;
}
push
@$xv
,
$car
if
$car
!= 0;
return
$xv
;
}
# shortcut for result $x == 0 => result = 0
return
$xv
if
@$xv
== 1 &&
$xv
->[0] == 0;
# since multiplying $x with $x fails, make copy in this case
$yv
=
$c
->_copy(
$xv
)
if
$xv
==
$yv
;
# same references?
my
@prod
= ();
my
(
$prod
,
$rem
,
$car
,
$cty
);
for
my
$xi
(
@$xv
) {
$car
= 0;
$cty
= 0;
# looping through this if $xi == 0 is silly - so optimize it away!
$xi
= (
shift
(
@prod
) || 0),
next
if
$xi
== 0;
for
my
$yi
(
@$yv
) {
$prod
=
$xi
*
$yi
+ (
$prod
[
$cty
] || 0) +
$car
;
$rem
=
$prod
%
$BASE
;
$car
= (
$prod
-
$rem
) /
$BASE
;
$prod
[
$cty
++] =
$rem
;
}
$prod
[
$cty
] +=
$car
if
$car
;
# need really to check for 0?
$xi
=
shift
(
@prod
) || 0;
# || 0 makes v5.005_3 happy
}
push
@$xv
,
@prod
;
$xv
;
}
sub
_div_use_int {
# ref to array, ref to array, modify first array and return remainder if
# in list context
# This version works on integers
my
(
$c
,
$x
,
$yorg
) =
@_
;
# the general div algorithm here is about O(N*N) and thus quite slow, so
# we first check for some special cases and use shortcuts to handle them.
# if both numbers have only one element:
if
(
@$x
== 1 &&
@$yorg
== 1) {
# shortcut, $yorg and $x are two small numbers
if
(
wantarray
) {
my
$rem
= [
$x
->[0] %
$yorg
->[0] ];
bless
$rem
,
$c
;
$x
->[0] =
$x
->[0] /
$yorg
->[0];
return
(
$x
,
$rem
);
}
else
{
$x
->[0] =
$x
->[0] /
$yorg
->[0];
return
$x
;
}
}
# if x has more than one, but y has only one element:
if
(
@$yorg
== 1) {
my
$rem
;
$rem
=
$c
->_mod(
$c
->_copy(
$x
),
$yorg
)
if
wantarray
;
# shortcut, $y is < $BASE
my
$j
=
@$x
;
my
$r
= 0;
my
$y
=
$yorg
->[0];
my
$b
;
while
(
$j
-- > 0) {
$b
=
$r
*
$BASE
+
$x
->[
$j
];
$r
=
$b
%
$y
;
$x
->[
$j
] =
$b
/
$y
;
}
pop
(
@$x
)
if
@$x
> 1 &&
$x
->[-1] == 0;
# remove any trailing zero
return
(
$x
,
$rem
)
if
wantarray
;
return
$x
;
}
# now x and y have more than one element
# check whether y has more elements than x, if so, the result is 0
if
(
@$yorg
>
@$x
) {
my
$rem
;
$rem
=
$c
->_copy(
$x
)
if
wantarray
;
# make copy
@$x
= 0;
# set to 0
return
(
$x
,
$rem
)
if
wantarray
;
# including remainder?
return
$x
;
# only x, which is [0] now
}
# check whether the numbers have the same number of elements, in that case
# the result will fit into one element and can be computed efficiently
if
(
@$yorg
==
@$x
) {
my
$cmp
= 0;
for
(
my
$j
=
$#$x
;
$j
>= 0 ; --
$j
) {
last
if
$cmp
=
$x
->[
$j
] -
$yorg
->[
$j
];
}
if
(
$cmp
== 0) {
# x = y
@$x
= 1;
return
$x
,
$c
->_zero()
if
wantarray
;
return
$x
;
}
if
(
$cmp
< 0) {
# x < y
if
(
wantarray
) {
my
$rem
=
$c
->_copy(
$x
);
@$x
= 0;
return
$x
,
$rem
;
}
@$x
= 0;
return
$x
;
}
}
# all other cases:
my
$y
=
$c
->_copy(
$yorg
);
# always make copy to preserve
my
$tmp
;
my
$dd
=
$BASE
/ (
$y
->[-1] + 1);
if
(
$dd
!= 1) {
my
$car
= 0;
for
my
$xi
(
@$x
) {
$xi
=
$xi
*
$dd
+
$car
;
$xi
-= (
$car
=
$xi
/
$BASE
) *
$BASE
;
}
push
(
@$x
,
$car
);
$car
= 0;
for
my
$yi
(
@$y
) {
$yi
=
$yi
*
$dd
+
$car
;
$yi
-= (
$car
=
$yi
/
$BASE
) *
$BASE
;
}
}
else
{
push
(
@$x
, 0);
}
# @q will accumulate the final result, $q contains the current computed
# part of the final result
my
@q
= ();
my
(
$v2
,
$v1
) =
@$y
[-2, -1];
$v2
= 0
unless
$v2
;
while
(
$#$x
>
$#$y
) {
my
(
$u2
,
$u1
,
$u0
) =
@$x
[-3 .. -1];
$u2
= 0
unless
$u2
;
#warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
# if $v1 == 0;
my
$tmp
=
$u0
*
$BASE
+
$u1
;
my
$rem
=
$tmp
%
$v1
;
my
$q
=
$u0
==
$v1
?
$MAX_VAL
: ((
$tmp
-
$rem
) /
$v1
);
--
$q
while
$v2
*
$q
> (
$u0
*
$BASE
+
$u1
-
$q
*
$v1
) *
$BASE
+
$u2
;
if
(
$q
) {
my
$prd
;
my
(
$car
,
$bar
) = (0, 0);
for
(
my
$yi
= 0,
my
$xi
=
$#$x
-
$#$y
- 1;
$yi
<=
$#$y
; ++
$yi
, ++
$xi
) {
$prd
=
$q
*
$y
->[
$yi
] +
$car
;
$prd
-= (
$car
=
int
(
$prd
/
$BASE
)) *
$BASE
;
$x
->[
$xi
] +=
$BASE
if
$bar
= ((
$x
->[
$xi
] -=
$prd
+
$bar
) < 0);
}
if
(
$x
->[-1] <
$car
+
$bar
) {
$car
= 0;
--
$q
;
for
(
my
$yi
= 0,
my
$xi
=
$#$x
-
$#$y
- 1;
$yi
<=
$#$y
; ++
$yi
, ++
$xi
) {
$x
->[
$xi
] -=
$BASE
if
$car
= ((
$x
->[
$xi
] +=
$y
->[
$yi
] +
$car
) >=
$BASE
);
}
}
}
pop
(
@$x
);
unshift
(
@q
,
$q
);
}
if
(
wantarray
) {
my
$d
=
bless
[],
$c
;
if
(
$dd
!= 1) {
my
$car
= 0;
my
$prd
;
for
my
$xi
(
reverse
@$x
) {
$prd
=
$car
*
$BASE
+
$xi
;
$car
=
$prd
- (
$tmp
=
$prd
/
$dd
) *
$dd
;
unshift
@$d
,
$tmp
;
}
}
else
{
@$d
=
@$x
;
}
@$x
=
@q
;
__strip_zeros(
$x
);
__strip_zeros(
$d
);
return
(
$x
,
$d
);
}
@$x
=
@q
;
__strip_zeros(
$x
);
$x
;
}
sub
_div_no_int {
# ref to array, ref to array, modify first array and return remainder if
# in list context
my
(
$c
,
$x
,
$yorg
) =
@_
;
# the general div algorithm here is about O(N*N) and thus quite slow, so
# we first check for some special cases and use shortcuts to handle them.
# if both numbers have only one element:
if
(
@$x
== 1 &&
@$yorg
== 1) {
# shortcut, $yorg and $x are two small numbers
my
$rem
= [
$x
->[0] %
$yorg
->[0] ];
bless
$rem
,
$c
;
$x
->[0] = (
$x
->[0] -
$rem
->[0]) /
$yorg
->[0];
return
(
$x
,
$rem
)
if
wantarray
;
return
$x
;
}
# if x has more than one, but y has only one element:
if
(
@$yorg
== 1) {
my
$rem
;
$rem
=
$c
->_mod(
$c
->_copy(
$x
),
$yorg
)
if
wantarray
;
# shortcut, $y is < $BASE
my
$j
=
@$x
;
my
$r
= 0;
my
$y
=
$yorg
->[0];
my
$b
;
while
(
$j
-- > 0) {
$b
=
$r
*
$BASE
+
$x
->[
$j
];
$r
=
$b
%
$y
;
$x
->[
$j
] = (
$b
-
$r
) /
$y
;
}
pop
(
@$x
)
if
@$x
> 1 &&
$x
->[-1] == 0;
# remove any trailing zero
return
(
$x
,
$rem
)
if
wantarray
;
return
$x
;
}
# now x and y have more than one element
# check whether y has more elements than x, if so, the result is 0
if
(
@$yorg
>
@$x
) {
my
$rem
;
$rem
=
$c
->_copy(
$x
)
if
wantarray
;
# make copy
@$x
= 0;
# set to 0
return
(
$x
,
$rem
)
if
wantarray
;
# including remainder?
return
$x
;
# only x, which is [0] now
}
# check whether the numbers have the same number of elements, in that case
# the result will fit into one element and can be computed efficiently
if
(
@$yorg
==
@$x
) {
my
$cmp
= 0;
for
(
my
$j
=
$#$x
;
$j
>= 0 ; --
$j
) {
last
if
$cmp
=
$x
->[
$j
] -
$yorg
->[
$j
];
}
if
(
$cmp
== 0) {
# x = y
@$x
= 1;
return
$x
,
$c
->_zero()
if
wantarray
;
return
$x
;
}
if
(
$cmp
< 0) {
# x < y
if
(
wantarray
) {
my
$rem
=
$c
->_copy(
$x
);
@$x
= 0;
return
$x
,
$rem
;
}
@$x
= 0;
return
$x
;
}
}
# all other cases:
my
$y
=
$c
->_copy(
$yorg
);
# always make copy to preserve
my
$tmp
=
$y
->[-1] + 1;
my
$rem
=
$BASE
%
$tmp
;
my
$dd
= (
$BASE
-
$rem
) /
$tmp
;
if
(
$dd
!= 1) {
my
$car
= 0;
for
my
$xi
(
@$x
) {
$xi
=
$xi
*
$dd
+
$car
;
$rem
=
$xi
%
$BASE
;
$car
= (
$xi
-
$rem
) /
$BASE
;
$xi
=
$rem
;
}
push
(
@$x
,
$car
);
$car
= 0;
for
my
$yi
(
@$y
) {
$yi
=
$yi
*
$dd
+
$car
;
$rem
=
$yi
%
$BASE
;
$car
= (
$yi
-
$rem
) /
$BASE
;
$yi
=
$rem
;
}
}
else
{
push
(
@$x
, 0);
}
# @q will accumulate the final result, $q contains the current computed
# part of the final result
my
@q
= ();
my
(
$v2
,
$v1
) =
@$y
[-2, -1];
$v2
= 0
unless
$v2
;
while
(
$#$x
>
$#$y
) {
my
(
$u2
,
$u1
,
$u0
) =
@$x
[-3 .. -1];
$u2
= 0
unless
$u2
;
#warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
# if $v1 == 0;
my
$tmp
=
$u0
*
$BASE
+
$u1
;
my
$rem
=
$tmp
%
$v1
;
my
$q
=
$u0
==
$v1
?
$MAX_VAL
: ((
$tmp
-
$rem
) /
$v1
);
--
$q
while
$v2
*
$q
> (
$u0
*
$BASE
+
$u1
-
$q
*
$v1
) *
$BASE
+
$u2
;
if
(
$q
) {
my
$prd
;
my
(
$car
,
$bar
) = (0, 0);
for
(
my
$yi
= 0,
my
$xi
=
$#$x
-
$#$y
- 1;
$yi
<=
$#$y
; ++
$yi
, ++
$xi
) {
$prd
=
$q
*
$y
->[
$yi
] +
$car
;
$rem
=
$prd
%
$BASE
;
$car
= (
$prd
-
$rem
) /
$BASE
;
$prd
-=
$car
*
$BASE
;
$x
->[
$xi
] +=
$BASE
if
$bar
= ((
$x
->[
$xi
] -=
$prd
+
$bar
) < 0);
}
if
(
$x
->[-1] <
$car
+
$bar
) {
$car
= 0;
--
$q
;
for
(
my
$yi
= 0,
my
$xi
=
$#$x
-
$#$y
- 1;
$yi
<=
$#$y
; ++
$yi
, ++
$xi
) {
$x
->[
$xi
] -=
$BASE
if
$car
= ((
$x
->[
$xi
] +=
$y
->[
$yi
] +
$car
) >=
$BASE
);
}
}
}
pop
(
@$x
);
unshift
(
@q
,
$q
);
}
if
(
wantarray
) {
my
$d
=
bless
[],
$c
;
if
(
$dd
!= 1) {
my
$car
= 0;
my
(
$prd
,
$rem
);
for
my
$xi
(
reverse
@$x
) {
$prd
=
$car
*
$BASE
+
$xi
;
$rem
=
$prd
%
$dd
;
$tmp
= (
$prd
-
$rem
) /
$dd
;
$car
=
$rem
;
unshift
@$d
,
$tmp
;
}
}
else
{
@$d
=
@$x
;
}
@$x
=
@q
;
__strip_zeros(
$x
);
__strip_zeros(
$d
);
return
(
$x
,
$d
);
}
@$x
=
@q
;
__strip_zeros(
$x
);
$x
;
}
##############################################################################
# testing
sub
_acmp {
# Internal absolute post-normalized compare (ignore signs)
# ref to array, ref to array, return <0, 0, >0
# Arrays must have at least one entry; this is not checked for.
my
(
$c
,
$cx
,
$cy
) =
@_
;
# shortcut for short numbers
return
((
$cx
->[0] <=>
$cy
->[0]) <=> 0)
if
@$cx
== 1 &&
@$cy
== 1;
# fast comp based on number of array elements (aka pseudo-length)
my
$lxy
= (
@$cx
-
@$cy
)
# or length of first element if same number of elements (aka difference 0)
||
# need int() here because sometimes the last element is '00018' vs '18'
(
length
(
int
(
$cx
->[-1])) -
length
(
int
(
$cy
->[-1])));
return
-1
if
$lxy
< 0;
# already differs, ret
return
1
if
$lxy
> 0;
# ditto
# manual way (abort if unequal, good for early ne)
my
$a
;
my
$j
=
@$cx
;
while
(--
$j
>= 0) {
last
if
$a
=
$cx
->[
$j
] -
$cy
->[
$j
];
}
$a
<=> 0;
}
sub
_len {
# compute number of digits in base 10
# int() because add/sub sometimes leaves strings (like '00005') instead of
# '5' in this place, thus causing length() to report wrong length
my
$cx
=
$_
[1];
(
@$cx
- 1) *
$BASE_LEN
+
length
(
int
(
$cx
->[-1]));
}
sub
_digit {
# Return the nth digit. Zero is rightmost, so _digit(123, 0) gives 3.
# Negative values count from the left, so _digit(123, -1) gives 1.
my
(
$c
,
$x
,
$n
) =
@_
;
my
$len
= _len(
''
,
$x
);
$n
+=
$len
if
$n
< 0;
# -1 last, -2 second-to-last
# Math::BigInt::Calc returns 0 if N is out of range, but this is not done
# by the other backend libraries.
return
"0"
if
$n
< 0 ||
$n
>=
$len
;
# return 0 for digits out of range
my
$elem
=
int
(
$n
/
$BASE_LEN
);
# index of array element
my
$digit
=
$n
%
$BASE_LEN
;
# index of digit within the element
substr
(
"0"
x
$BASE_LEN
.
"$x->[$elem]"
, -1 -
$digit
, 1);
}
sub
_zeros {
# Return number of trailing zeros in decimal.
# Check each array element for having 0 at end as long as elem == 0
# Upon finding a elem != 0, stop.
my
$x
=
$_
[1];
return
0
if
@$x
== 1 &&
$x
->[0] == 0;
my
$zeros
= 0;
foreach
my
$elem
(
@$x
) {
if
(
$elem
!= 0) {
$elem
=~ /[^0](0*)\z/;
$zeros
+=
length
($1);
# count trailing zeros
last
;
# early out
}
$zeros
+=
$BASE_LEN
;
}
$zeros
;
}
##############################################################################
# _is_* routines
sub
_is_zero {
# return true if arg is zero
@{
$_
[1]} == 1 &&
$_
[1]->[0] == 0 ? 1 : 0;
}
sub
_is_even {
# return true if arg is even
$_
[1]->[0] % 2 ? 0 : 1;
}
sub
_is_odd {
# return true if arg is odd
$_
[1]->[0] % 2 ? 1 : 0;
}
sub
_is_one {
# return true if arg is one
@{
$_
[1]} == 1 &&
$_
[1]->[0] == 1 ? 1 : 0;
}
sub
_is_two {
# return true if arg is two
@{
$_
[1]} == 1 &&
$_
[1]->[0] == 2 ? 1 : 0;
}
sub
_is_ten {
# return true if arg is ten
if
(
$BASE_LEN
== 1) {
@{
$_
[1]} == 2 &&
$_
[1]->[0] == 0 &&
$_
[1]->[1] == 1 ? 1 : 0;
}
else
{
@{
$_
[1]} == 1 &&
$_
[1]->[0] == 10 ? 1 : 0;
}
}
sub
__strip_zeros {
# Internal normalization function that strips leading zeros from the array.
# Args: ref to array
my
$x
=
shift
;
push
@$x
, 0
if
@$x
== 0;
# div might return empty results, so fix it
return
$x
if
@$x
== 1;
# early out
#print "strip: cnt $cnt i $i\n";
# '0', '3', '4', '0', '0',
# 0 1 2 3 4
# cnt = 5, i = 4
# i = 4
# i = 3
# => fcnt = cnt - i (5-2 => 3, cnt => 5-1 = 4, throw away from 4th pos)
# >= 1: skip first part (this can be zero)
my
$i
=
$#$x
;
while
(
$i
> 0) {
last
if
$x
->[
$i
] != 0;
$i
--;
}
$i
++;
splice
(
@$x
,
$i
)
if
$i
<
@$x
;
$x
;
}
###############################################################################
# check routine to test internal state for corruptions
sub
_check {
# used by the test suite
my
(
$class
,
$x
) =
@_
;
my
$msg
=
$class
-> SUPER::_check(
$x
);
return
$msg
if
$msg
;
my
$n
;
eval
{
$n
=
@$x
};
return
"Not an array reference"
unless
$@ eq
''
;
return
"Reference to an empty array"
unless
$n
> 0;
# The following fails with Math::BigInt::FastCalc because a
# Math::BigInt::FastCalc "object" is an unblessed array ref.
#
#return 0 unless ref($x) eq $class;
for
(
my
$i
= 0 ;
$i
<=
$#$x
; ++
$i
) {
my
$e
=
$x
-> [
$i
];
return
"Element at index $i is undefined"
unless
defined
$e
;
return
"Element at index $i is a '"
.
ref
(
$e
) .
"', which is not a scalar"
unless
ref
(
$e
) eq
""
;
# It would be better to use the regex /^([1-9]\d*|0)\z/, but that fails
# in Math::BigInt::FastCalc, because it sometimes creates array
# elements like "000000".
return
"Element at index $i is '$e', which does not look like an"
.
" normal integer"
unless
$e
=~ /^\d+\z/;
return
"Element at index $i is '$e', which is not smaller than"
.
" the base '$BASE'"
if
$e
>=
$BASE
;
return
"Element at index $i (last element) is zero"
if
$#$x
> 0 &&
$i
==
$#$x
&&
$e
== 0;
}
return
0;
}
###############################################################################
sub
_mod {
# if possible, use mod shortcut
my
(
$c
,
$x
,
$yo
) =
@_
;
# slow way since $y too big
if
(
@$yo
> 1) {
my
(
$xo
,
$rem
) =
$c
->_div(
$x
,
$yo
);
@$x
=
@$rem
;
return
$x
;
}
my
$y
=
$yo
->[0];
# if both are single element arrays
if
(
@$x
== 1) {
$x
->[0] %=
$y
;
return
$x
;
}
# if @$x has more than one element, but @$y is a single element
my
$b
=
$BASE
%
$y
;
if
(
$b
== 0) {
# when BASE % Y == 0 then (B * BASE) % Y == 0
# (B * BASE) % $y + A % Y => A % Y
# so need to consider only last element: O(1)
$x
->[0] %=
$y
;
}
elsif
(
$b
== 1) {
# else need to go through all elements in @$x: O(N), but loop is a bit
# simplified
my
$r
= 0;
foreach
(
@$x
) {
$r
= (
$r
+
$_
) %
$y
;
# not much faster, but heh...
#$r += $_ % $y; $r %= $y;
}
$r
= 0
if
$r
==
$y
;
$x
->[0] =
$r
;
}
else
{
# else need to go through all elements in @$x: O(N)
my
$r
= 0;
my
$bm
= 1;
foreach
(
@$x
) {
$r
= (
$_
*
$bm
+
$r
) %
$y
;
$bm
= (
$bm
*
$b
) %
$y
;
#$r += ($_ % $y) * $bm;
#$bm *= $b;
#$bm %= $y;
#$r %= $y;
}
$r
= 0
if
$r
==
$y
;
$x
->[0] =
$r
;
}
@$x
=
$x
->[0];
# keep one element of @$x
return
$x
;
}
##############################################################################
# shifts
sub
_rsft {
my
(
$c
,
$x
,
$n
,
$b
) =
@_
;
return
$x
if
$c
->_is_zero(
$x
) ||
$c
->_is_zero(
$n
);
# For backwards compatibility, allow the base $b to be a scalar.
$b
=
$c
->_new(
$b
)
unless
ref
$b
;
if
(
$c
-> _acmp(
$b
,
$c
-> _ten())) {
return
scalar
$c
->_div(
$x
,
$c
->_pow(
$c
->_copy(
$b
),
$n
));
}
# shortcut (faster) for shifting by 10)
# multiples of $BASE_LEN
my
$dst
= 0;
# destination
my
$src
=
$c
->_num(
$n
);
# as normal int
my
$xlen
= (
@$x
- 1) *
$BASE_LEN
+
length
(
int
(
$x
->[-1]));
if
(
$src
>=
$xlen
or (
$src
==
$xlen
and !
defined
$x
->[1])) {
# 12345 67890 shifted right by more than 10 digits => 0
splice
(
@$x
, 1);
# leave only one element
$x
->[0] = 0;
# set to zero
return
$x
;
}
my
$rem
=
$src
%
$BASE_LEN
;
# remainder to shift
$src
=
int
(
$src
/
$BASE_LEN
);
# source
if
(
$rem
== 0) {
splice
(
@$x
, 0,
$src
);
# even faster, 38.4 => 39.3
}
else
{
my
$len
=
@$x
-
$src
;
# elems to go
my
$vd
;
my
$z
=
'0'
x
$BASE_LEN
;
$x
->[
@$x
] = 0;
# avoid || 0 test inside loop
while
(
$dst
<
$len
) {
$vd
=
$z
.
$x
->[
$src
];
$vd
=
substr
(
$vd
, -
$BASE_LEN
,
$BASE_LEN
-
$rem
);
$src
++;
$vd
=
substr
(
$z
.
$x
->[
$src
], -
$rem
,
$rem
) .
$vd
;
$vd
=
substr
(
$vd
, -
$BASE_LEN
,
$BASE_LEN
)
if
length
(
$vd
) >
$BASE_LEN
;
$x
->[
$dst
] =
int
(
$vd
);
$dst
++;
}
splice
(
@$x
,
$dst
)
if
$dst
> 0;
# kill left-over array elems
pop
(
@$x
)
if
$x
->[-1] == 0 &&
@$x
> 1;
# kill last element if 0
}
# else rem == 0
$x
;
}
sub
_lsft {
my
(
$c
,
$x
,
$n
,
$b
) =
@_
;
return
$x
if
$c
->_is_zero(
$x
) ||
$c
->_is_zero(
$n
);
# For backwards compatibility, allow the base $b to be a scalar.
$b
=
$c
->_new(
$b
)
unless
ref
$b
;
# If the base is a power of 10, use shifting, since the internal
# representation is in base 10eX.
my
$bstr
=
$c
->_str(
$b
);
if
(
$bstr
=~ /^1(0+)\z/) {
# Adjust $n so that we're shifting in base 10. Do this by multiplying
# $n by the base 10 logarithm of $b: $b ** $n = 10 ** (log10($b) * $n).
my
$log10b
=
length
($1);
$n
=
$c
->_mul(
$c
->_new(
$log10b
),
$n
);
$n
=
$c
->_num(
$n
);
# shift-len as normal int
# $q is the number of places to shift the elements within the array,
# and $r is the number of places to shift the values within the
# elements.
my
$r
=
$n
%
$BASE_LEN
;
my
$q
= (
$n
-
$r
) /
$BASE_LEN
;
# If we must shift the values within the elements ...
if
(
$r
) {
my
$i
=
@$x
;
# index
$x
->[
$i
] = 0;
# initialize most significant element
my
$z
=
'0'
x
$BASE_LEN
;
my
$vd
;
while
(
$i
>= 0) {
$vd
=
$x
->[
$i
];
$vd
=
$z
.
$vd
;
$vd
=
substr
(
$vd
,
$r
-
$BASE_LEN
,
$BASE_LEN
-
$r
);
$vd
.=
$i
> 0 ?
substr
(
$z
.
$x
->[
$i
- 1], -
$BASE_LEN
,
$r
)
:
'0'
x
$r
;
$vd
=
substr
(
$vd
, -
$BASE_LEN
,
$BASE_LEN
)
if
length
(
$vd
) >
$BASE_LEN
;
$x
->[
$i
] =
int
(
$vd
);
# e.g., "0...048" -> 48 etc.
$i
--;
}
pop
(
@$x
)
if
$x
->[-1] == 0;
# if most significant element is zero
}
# If we must shift the elements within the array ...
if
(
$q
) {
unshift
@$x
, (0) x
$q
;
}
}
else
{
$x
=
$c
->_mul(
$x
,
$c
->_pow(
$b
,
$n
));
}
return
$x
;
}
sub
_pow {
# power of $x to $y
# ref to array, ref to array, return ref to array
my
(
$c
,
$cx
,
$cy
) =
@_
;
if
(
@$cy
== 1 &&
$cy
->[0] == 0) {
splice
(
@$cx
, 1);
$cx
->[0] = 1;
# y == 0 => x => 1
return
$cx
;
}
if
((
@$cx
== 1 &&
$cx
->[0] == 1) ||
# x == 1
(
@$cy
== 1 &&
$cy
->[0] == 1))
# or y == 1
{
return
$cx
;
}
if
(
@$cx
== 1 &&
$cx
->[0] == 0) {
splice
(
@$cx
, 1);
$cx
->[0] = 0;
# 0 ** y => 0 (if not y <= 0)
return
$cx
;
}
my
$pow2
=
$c
->_one();
my
$y_bin
=
$c
->_as_bin(
$cy
);
$y_bin
=~ s/^0b//;
my
$len
=
length
(
$y_bin
);
while
(--
$len
> 0) {
$c
->_mul(
$pow2
,
$cx
)
if
substr
(
$y_bin
,
$len
, 1) eq
'1'
;
# is odd?
$c
->_mul(
$cx
,
$cx
);
}
$c
->_mul(
$cx
,
$pow2
);
$cx
;
}
sub
_nok {
# Return binomial coefficient (n over k).
# Given refs to arrays, return ref to array.
# First input argument is modified.
my
(
$c
,
$n
,
$k
) =
@_
;
# If k > n/2, or, equivalently, 2*k > n, compute nok(n, k) as
# nok(n, n-k), to minimize the number if iterations in the loop.
{
my
$twok
=
$c
->_mul(
$c
->_two(),
$c
->_copy(
$k
));
# 2 * k
if
(
$c
->_acmp(
$twok
,
$n
) > 0) {
# if 2*k > n
$k
=
$c
->_sub(
$c
->_copy(
$n
),
$k
);
# k = n - k
}
}
# Example:
#
# / 7 \ 7! 1*2*3*4 * 5*6*7 5 * 6 * 7 6 7
# | | = --------- = --------------- = --------- = 5 * - * -
# \ 3 / (7-3)! 3! 1*2*3*4 * 1*2*3 1 * 2 * 3 2 3
if
(
$c
->_is_zero(
$k
)) {
@$n
= 1;
}
else
{
# Make a copy of the original n, since we'll be modifying n in-place.
my
$n_orig
=
$c
->_copy(
$n
);
# n = 5, f = 6, d = 2 (cf. example above)
$c
->_sub(
$n
,
$k
);
$c
->_inc(
$n
);
my
$f
=
$c
->_copy(
$n
);
$c
->_inc(
$f
);
my
$d
=
$c
->_two();
# while f <= n (the original n, that is) ...
while
(
$c
->_acmp(
$f
,
$n_orig
) <= 0) {
# n = (n * f / d) == 5 * 6 / 2 (cf. example above)
$c
->_mul(
$n
,
$f
);
$c
->_div(
$n
,
$d
);
# f = 7, d = 3 (cf. example above)
$c
->_inc(
$f
);
$c
->_inc(
$d
);
}
}
return
$n
;
}
sub
_fac {
# factorial of $x
# ref to array, return ref to array
my
(
$c
,
$cx
) =
@_
;
# We cache the smallest values. Don't assume that a single element has a
# value larger than 9 or else it won't work with a $BASE_LEN of 1.
if
(
@$cx
== 1) {
my
@factorials
=
(
'1'
,
'1'
,
'2'
,
'6'
,
'24'
,
'120'
,
'720'
,
'5040'
,
'40320'
,
'362880'
,
);
if
(
$cx
->[0] <=
$#factorials
) {
my
$tmp
=
$c
-> _new(
$factorials
[
$cx
->[0] ]);
@$cx
=
@$tmp
;
return
$cx
;
}
}
# The old code further below doesn't work for small values of $BASE_LEN.
# Alas, I have not been able to (or taken the time to) decipher it, so for
# the case when $BASE_LEN is small, we call the parent class. This code
# works in for every value of $x and $BASE_LEN. We could use this code for
# all cases, but it is a little slower than the code further below, so at
# least for now we keep the code below.
if
(
$BASE_LEN
<= 2) {
my
$tmp
=
$c
-> SUPER::_fac(
$cx
);
@$cx
=
@$tmp
;
return
$cx
;
}
# This code does not work for small values of $BASE_LEN.
if
((
@$cx
== 1) &&
# we do this only if $x >= 12 and $x <= 7000
(
$cx
->[0] >= 12 &&
$cx
->[0] < 7000)) {
# Calculate (k-j) * (k-j+1) ... k .. (k+j-1) * (k + j)
# The above series can be expressed as factors:
# k * k - (j - i) * 2
# We cache k*k, and calculate (j * j) as the sum of the first j odd integers
# This will not work when N exceeds the storage of a Perl scalar, however,
# in this case the algorithm would be way too slow to terminate, anyway.
# As soon as the last element of $cx is 0, we split it up and remember
# how many zeors we got so far. The reason is that n! will accumulate
# zeros at the end rather fast.
my
$zero_elements
= 0;
# If n is even, set n = n -1
my
$k
=
$c
->_num(
$cx
);
my
$even
= 1;
if
((
$k
& 1) == 0) {
$even
=
$k
;
$k
--;
}
# set k to the center point
$k
= (
$k
+ 1) / 2;
# print "k $k even: $even\n";
# now calculate k * k
my
$k2
=
$k
*
$k
;
my
$odd
= 1;
my
$sum
= 1;
my
$i
=
$k
- 1;
# keep reference to x
my
$new_x
=
$c
->_new(
$k
*
$even
);
@$cx
=
@$new_x
;
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
# print STDERR "x = ", $c->_str($cx), "\n";
my
$BASE2
=
int
(
sqrt
(
$BASE
))-1;
my
$j
= 1;
while
(
$j
<=
$i
) {
my
$m
= (
$k2
-
$sum
);
$odd
+= 2;
$sum
+=
$odd
;
$j
++;
while
(
$j
<=
$i
&& (
$m
<
$BASE2
) && ((
$k2
-
$sum
) <
$BASE2
)) {
$m
*= (
$k2
-
$sum
);
$odd
+= 2;
$sum
+=
$odd
;
$j
++;
# print STDERR "\n k2 $k2 m $m sum $sum odd $odd\n"; sleep(1);
}
if
(
$m
<
$BASE
) {
$c
->_mul(
$cx
, [
$m
]);
}
else
{
$c
->_mul(
$cx
,
$c
->_new(
$m
));
}
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
# print STDERR "Calculate $k2 - $sum = $m (x = ", $c->_str($cx), ")\n";
}
# multiply in the zeros again
unshift
@$cx
, (0) x
$zero_elements
;
return
$cx
;
}
# go forward until $base is exceeded limit is either $x steps (steps == 100
# means a result always too high) or $base.
my
$steps
= 100;
$steps
=
$cx
->[0]
if
@$cx
== 1;
my
$r
= 2;
my
$cf
= 3;
my
$step
= 2;
my
$last
=
$r
;
while
(
$r
*
$cf
<
$BASE
&&
$step
<
$steps
) {
$last
=
$r
;
$r
*=
$cf
++;
$step
++;
}
if
((
@$cx
== 1) &&
$step
==
$cx
->[0]) {
# completely done, so keep reference to $x and return
$cx
->[0] =
$r
;
return
$cx
;
}
# now we must do the left over steps
my
$n
;
# steps still to do
if
(
@$cx
== 1) {
$n
=
$cx
->[0];
}
else
{
$n
=
$c
->_copy(
$cx
);
}
# Set $cx to the last result below $BASE (but keep ref to $x)
$cx
->[0] =
$last
;
splice
(
@$cx
, 1);
# As soon as the last element of $cx is 0, we split it up and remember
# how many zeors we got so far. The reason is that n! will accumulate
# zeros at the end rather fast.
my
$zero_elements
= 0;
# do left-over steps fit into a scalar?
if
(
ref
$n
eq
'ARRAY'
) {
# No, so use slower inc() & cmp()
# ($n is at least $BASE here)
my
$base_2
=
int
(
sqrt
(
$BASE
)) - 1;
#print STDERR "base_2: $base_2\n";
while
(
$step
<
$base_2
) {
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
my
$b
=
$step
* (
$step
+ 1);
$step
+= 2;
$c
->_mul(
$cx
, [
$b
]);
}
$step
= [
$step
];
while
(
$c
->_acmp(
$step
,
$n
) <= 0) {
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
$c
->_mul(
$cx
,
$step
);
$c
->_inc(
$step
);
}
}
else
{
# Yes, so we can speed it up slightly
# print "# left over steps $n\n";
my
$base_4
=
int
(
sqrt
(
sqrt
(
$BASE
))) - 2;
#print STDERR "base_4: $base_4\n";
my
$n4
=
$n
- 4;
while
(
$step
<
$n4
&&
$step
<
$base_4
) {
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
my
$b
=
$step
* (
$step
+ 1);
$step
+= 2;
$b
*=
$step
* (
$step
+ 1);
$step
+= 2;
$c
->_mul(
$cx
, [
$b
]);
}
my
$base_2
=
int
(
sqrt
(
$BASE
)) - 1;
my
$n2
=
$n
- 2;
#print STDERR "base_2: $base_2\n";
while
(
$step
<
$n2
&&
$step
<
$base_2
) {
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
my
$b
=
$step
* (
$step
+ 1);
$step
+= 2;
$c
->_mul(
$cx
, [
$b
]);
}
# do what's left over
while
(
$step
<=
$n
) {
$c
->_mul(
$cx
, [
$step
]);
$step
++;
if
(
$cx
->[0] == 0) {
$zero_elements
++;
shift
@$cx
;
}
}
}
# multiply in the zeros again
unshift
@$cx
, (0) x
$zero_elements
;
$cx
;
# return result
}
sub
_log_int {
# calculate integer log of $x to base $base
# ref to array, ref to array - return ref to array
my
(
$c
,
$x
,
$base
) =
@_
;
# X == 0 => NaN
return
if
@$x
== 1 &&
$x
->[0] == 0;
# BASE 0 or 1 => NaN
return
if
@$base
== 1 &&
$base
->[0] < 2;
# X == 1 => 0 (is exact)
if
(
@$x
== 1 &&
$x
->[0] == 1) {
@$x
= 0;
return
$x
, 1;
}
my
$cmp
=
$c
->_acmp(
$x
,
$base
);
# X == BASE => 1 (is exact)
if
(
$cmp
== 0) {
@$x
= 1;
return
$x
, 1;
}
# 1 < X < BASE => 0 (is truncated)
if
(
$cmp
< 0) {
@$x
= 0;
return
$x
, 0;
}
my
$x_org
=
$c
->_copy(
$x
);
# preserve x
# Compute a guess for the result based on:
# $guess = int ( length_in_base_10(X) / ( log(base) / log(10) ) )
my
$len
=
$c
->_len(
$x_org
);
my
$log
=
log
(
$base
->[-1]) /
log
(10);
# for each additional element in $base, we add $BASE_LEN to the result,
# based on the observation that log($BASE, 10) is BASE_LEN and
# log(x*y) == log(x) + log(y):
$log
+= (
@$base
- 1) *
$BASE_LEN
;
# calculate now a guess based on the values obtained above:
my
$res
=
$c
->_new(
int
(
$len
/
$log
));
@$x
=
@$res
;
my
$trial
=
$c
->_pow(
$c
->_copy(
$base
),
$x
);
my
$acmp
=
$c
->_acmp(
$trial
,
$x_org
);
# Did we get the exact result?
return
$x
, 1
if
$acmp
== 0;
# Too small?
while
(
$acmp
< 0) {
$c
->_mul(
$trial
,
$base
);
$c
->_inc(
$x
);
$acmp
=
$c
->_acmp(
$trial
,
$x_org
);
}
# Too big?
while
(
$acmp
> 0) {
$c
->_div(
$trial
,
$base
);
$c
->_dec(
$x
);
$acmp
=
$c
->_acmp(
$trial
,
$x_org
);
}
return
$x
, 1
if
$acmp
== 0;
# result is exact
return
$x
, 0;
# result is too small
}
sub
_ilog2 {
# calculate int(log2($x))
# There is virtually nothing to gain from computing this any differently
# than _log_int(), but it is important that we don't use the method
# inherited from the parent, because that method is very slow for backend
# libraries whose internal representation uses base 10.
my
(
$c
,
$x
) =
@_
;
(
$x
,
my
$is_exact
) =
$c
-> _log_int(
$x
,
$c
-> _two());
return
wantarray
? (
$x
,
$is_exact
) :
$x
;
}
sub
_ilog10 {
# calculate int(log10($x))
my
(
$c
,
$x
) =
@_
;
# X == 0 => NaN
return
if
@$x
== 1 &&
$x
->[0] == 0;
# X == 1 => 0 (is exact)
if
(
@$x
== 1 &&
$x
->[0] == 1) {
@$x
= 0;
return
wantarray
? (
$x
, 1) :
$x
;
}
my
$x_orig
=
$c
-> _copy(
$x
);
my
$nm1
=
$c
-> _len(
$x
) - 1;
my
$xtmp
=
$c
-> _new(
$nm1
);
@$x
=
@$xtmp
;
return
$x
unless
wantarray
;
# See if the original $x is an exact power of 10, in which case all but the
# most significan chunks are 0, and the most significant chunk is a power
# of 10.
my
$is_pow10
= 1;
for
my
$i
(0 ..
$#$x_orig
- 1) {
last
unless
$is_pow10
=
$x_orig
->[
$i
] == 0;
}
$is_pow10
&&=
$x_orig
->[-1] == 10*
*int
(0.5 +
log
(
$x_orig
->[-1]) /
log
(10));
return
wantarray
? (
$x
, 1) :
$x
if
$is_pow10
;
return
wantarray
? (
$x
, 0) :
$x
;
}
sub
_clog2 {
# calculate ceil(log2($x))
my
(
$c
,
$x
) =
@_
;
# X == 0 => NaN
return
if
@$x
== 1 &&
$x
->[0] == 0;
# X == 1 => 0 (is exact)
if
(
@$x
== 1 &&
$x
->[0] == 1) {
@$x
= 0;
return
wantarray
? (
$x
, 1) :
$x
;
}
my
$base
=
$c
-> _two();
my
$acmp
=
$c
-> _acmp(
$x
,
$base
);
# X == BASE => 1 (is exact)
if
(
$acmp
== 0) {
@$x
= 1;
return
wantarray
? (
$x
, 1) :
$x
;
}
# 1 < X < BASE => 0 (is truncated)
if
(
$acmp
< 0) {
@$x
= 0;
return
wantarray
? (
$x
, 0) :
$x
;
}
# Compute a guess for the result based on:
# $guess = int( length_in_base_10(X) / (log(base) / log(10)) )
my
$len
=
$c
-> _len(
$x
);
my
$log
=
log
(2) /
log
(10);
my
$guess
=
$c
-> _new(
int
(
$len
/
$log
));
my
$x_orig
=
$c
-> _copy(
$x
);
@$x
=
@$guess
;
my
$trial
=
$c
-> _pow(
$c
-> _copy(
$base
),
$x
);
$acmp
=
$c
-> _acmp(
$trial
,
$x_orig
);
# Too big?
while
(
$acmp
> 0) {
$c
-> _div(
$trial
,
$base
);
$c
-> _dec(
$x
);
$acmp
=
$c
-> _acmp(
$trial
,
$x_orig
);
}
# Too small?
while
(
$acmp
< 0) {
$c
-> _mul(
$trial
,
$base
);
$c
-> _inc(
$x
);
$acmp
=
$c
-> _acmp(
$trial
,
$x_orig
);
}
return
wantarray
? (
$x
, 1) :
$x
if
$acmp
== 0;
# result is exact
return
wantarray
? (
$x
, 0) :
$x
;
# result is too small
}
sub
_clog10 {
# calculate ceil(log2($x))
my
(
$c
,
$x
) =
@_
;
# X == 0 => NaN
return
if
@$x
== 1 &&
$x
->[0] == 0;
# X == 1 => 0 (is exact)
if
(
@$x
== 1 &&
$x
->[0] == 1) {
@$x
= 0;
return
wantarray
? (
$x
, 1) :
$x
;
}
# Get the number of base 10 digits. $n is the desired output, except when
# $x is an exact power of 10, in which case $n is 1 too big.
my
$n
=
$c
-> _len(
$x
);
# See if $x is an exact power of 10, in which case all but the most
# significan chunks are 0, and the most significant chunk is a power of 10.
my
$is_pow10
= 1;
for
my
$i
(0 ..
$#$x
- 1) {
last
unless
$is_pow10
=
$x
->[
$i
] == 0;
}
$is_pow10
&&=
$x
->[-1] == 10*
*int
(0.5 +
log
(
$x
->[-1]) /
log
(10));
$n
--
if
$is_pow10
;
my
$xtmp
=
$c
->_new(
$n
);
@$x
=
@$xtmp
;
return
wantarray
? (
$x
, 1) :
$x
if
$is_pow10
;
# result is exact
return
wantarray
? (
$x
, 0) :
$x
;
# result is too small
}
# for debugging:
my
$steps
= 0;
sub
steps {
$steps
};
sub
_sqrt {
# square-root of $x in-place
my
(
$c
,
$x
) =
@_
;
if
(
@$x
== 1) {
# fits into one Perl scalar, so result can be computed directly
$x
->[0] =
int
(
sqrt
(
$x
->[0]));
return
$x
;
}
# Create an initial guess for the square root.
my
$s
;
if
(
@$x
% 2) {
$s
= [ (0) x ((
@$x
- 1) / 2),
int
(
sqrt
(
$x
->[-1])) ];
}
else
{
$s
= [ (0) x ((
@$x
- 2) / 2),
int
(
sqrt
(
$x
->[-2] +
$x
->[-1] *
$BASE
)) ];
}
# Newton's method for the square root of y:
#
# x(n) * x(n) - y
# x(n+1) = x(n) - -----------------
# 2 * x(n)
my
$cmp
;
while
(1) {
my
$sq
=
$c
-> _mul(
$c
-> _copy(
$s
),
$s
);
$cmp
=
$c
-> _acmp(
$sq
,
$x
);
# If x(n)*x(n) > y, compute
#
# x(n) * x(n) - y
# x(n+1) = x(n) - -----------------
# 2 * x(n)
if
(
$cmp
> 0) {
my
$num
=
$c
-> _sub(
$c
-> _copy(
$sq
),
$x
);
my
$den
=
$c
-> _mul(
$c
-> _two(),
$s
);
my
$delta
=
$c
-> _div(
$num
,
$den
);
last
if
$c
-> _is_zero(
$delta
);
$s
=
$c
-> _sub(
$s
,
$delta
);
}
# If x(n)*x(n) < y, compute
#
# y - x(n) * x(n)
# x(n+1) = x(n) + -----------------
# 2 * x(n)
elsif
(
$cmp
< 0) {
my
$num
=
$c
-> _sub(
$c
-> _copy(
$x
),
$sq
);
my
$den
=
$c
-> _mul(
$c
-> _two(),
$s
);
my
$delta
=
$c
-> _div(
$num
,
$den
);
last
if
$c
-> _is_zero(
$delta
);
$s
=
$c
-> _add(
$s
,
$delta
);
}
# If x(n)*x(n) = y, we have the exact result.
else
{
last
;
}
}
$s
=
$c
-> _dec(
$s
)
if
$cmp
> 0;
# never overshoot
@$x
=
@$s
;
return
$x
;
}
sub
_root {
# Take n'th root of $x in place.
my
(
$c
,
$x
,
$n
) =
@_
;
# Small numbers.
if
(
@$x
== 1) {
return
$x
if
$x
-> [0] == 0 ||
$x
-> [0] == 1;
if
(
@$n
== 1) {
# Result can be computed directly. Adjust initial result for
# numerical errors, e.g., int(1000**(1/3)) is 2, not 3.
my
$y
=
int
(
$x
->[0] ** (1 /
$n
->[0]));
my
$yp1
=
$y
+ 1;
$y
=
$yp1
if
$yp1
**
$n
->[0] ==
$x
->[0];
$x
->[0] =
$y
;
return
$x
;
}
}
# If x <= n, the result is always (truncated to) 1.
if
((
@$x
> 1 ||
$x
-> [0] > 0) &&
# if x is non-zero ...
$c
-> _acmp(
$x
,
$n
) <= 0)
# ... and x <= n
{
my
$one
=
$c
-> _one();
@$x
=
@$one
;
return
$x
;
}
# If $n is a power of two, take sqrt($x) repeatedly, e.g., root($x, 4) =
# sqrt(sqrt($x)), root($x, 8) = sqrt(sqrt(sqrt($x))).
my
$b
=
$c
-> _as_bin(
$n
);
if
(
$b
=~ /0b1(0+)$/) {
my
$count
=
length
($1);
# 0b100 => len('00') => 2
my
$cnt
=
$count
;
# counter for loop
unshift
@$x
, 0;
# add one element, together with one
# more below in the loop this makes 2
while
(
$cnt
-- > 0) {
# 'Inflate' $x by adding one element, basically computing
# $x * $BASE * $BASE. This gives us more $BASE_LEN digits for
# result since len(sqrt($X)) approx == len($x) / 2.
unshift
@$x
, 0;
# Calculate sqrt($x), $x is now one element to big, again. In the
# next round we make that two, again.
$c
-> _sqrt(
$x
);
}
# $x is now one element too big, so truncate result by removing it.
shift
@$x
;
return
$x
;
}
my
$DEBUG
= 0;
# Now the general case. This works by finding an initial guess. If this
# guess is incorrect, a relatively small delta is chosen. This delta is
# used to find a lower and upper limit for the correct value. The delta is
# doubled in each iteration. When a lower and upper limit is found,
# bisection is applied to narrow down the region until we have the correct
# value.
# Split x into mantissa and exponent in base 10, so that
#
# x = xm * 10^xe, where 0 < xm < 1 and xe is an integer
my
$x_str
=
$c
-> _str(
$x
);
my
$xm
=
"."
.
$x_str
;
my
$xe
=
length
(
$x_str
);
# From this we compute the base 10 logarithm of x
#
# log_10(x) = log_10(xm) + log_10(xe^10)
# = log(xm)/log(10) + xe
#
# and then the base 10 logarithm of y, where y = x^(1/n)
#
# log_10(y) = log_10(x)/n
my
$log10x
=
log
(
$xm
) /
log
(10) +
$xe
;
my
$log10y
=
$log10x
/
$c
-> _num(
$n
);
# And from this we compute ym and ye, the mantissa and exponent (in
# base 10) of y, where 1 < ym <= 10 and ye is an integer.
my
$ye
=
int
$log10y
;
my
$ym
= 10 ** (
$log10y
-
$ye
);
# Finally, we scale the mantissa and exponent to incraese the integer
# part of ym, before building the string representing our guess of y.
if
(
$DEBUG
) {
"\n"
;
"xm = $xm\n"
;
"xe = $xe\n"
;
"log10x = $log10x\n"
;
"log10y = $log10y\n"
;
"ym = $ym\n"
;
"ye = $ye\n"
;
"\n"
;
}
my
$d
=
$ye
< 15 ?
$ye
: 15;
$ym
*= 10 **
$d
;
$ye
-=
$d
;
my
$y_str
=
sprintf
(
'%.0f'
,
$ym
) .
"0"
x
$ye
;
my
$y
=
$c
-> _new(
$y_str
);
if
(
$DEBUG
) {
"ym = $ym\n"
;
"ye = $ye\n"
;
"\n"
;
"y_str = $y_str (initial guess)\n"
;
"\n"
;
}
# See if our guess y is correct.
my
$trial
=
$c
-> _pow(
$c
-> _copy(
$y
),
$n
);
my
$acmp
=
$c
-> _acmp(
$trial
,
$x
);
if
(
$acmp
== 0) {
@$x
=
@$y
;
return
$x
;
}
# Find a lower and upper limit for the correct value of y. Start off with a
# delta value that is approximately the size of the accuracy of the guess.
my
$lower
;
my
$upper
;
my
$delta
=
$c
-> _new(
"1"
. (
"0"
x
$ye
));
my
$two
=
$c
-> _two();
if
(
$acmp
< 0) {
$lower
=
$y
;
while
(
$acmp
< 0) {
$upper
=
$c
-> _add(
$c
-> _copy(
$lower
),
$delta
);
if
(
$DEBUG
) {
"lower = $lower\n"
;
"upper = $upper\n"
;
"delta = $delta\n"
;
"\n"
;
}
$acmp
=
$c
-> _acmp(
$c
-> _pow(
$c
-> _copy(
$upper
),
$n
),
$x
);
if
(
$acmp
== 0) {
@$x
=
@$upper
;
return
$x
;
}
$delta
=
$c
-> _mul(
$delta
,
$two
);
}
}
elsif
(
$acmp
> 0) {
$upper
=
$y
;
while
(
$acmp
> 0) {
if
(
$c
-> _acmp(
$upper
,
$delta
) <= 0) {
$lower
=
$c
-> _zero();
last
;
}
$lower
=
$c
-> _sub(
$c
-> _copy(
$upper
),
$delta
);
if
(
$DEBUG
) {
"lower = $lower\n"
;
"upper = $upper\n"
;
"delta = $delta\n"
;
"\n"
;
}
$acmp
=
$c
-> _acmp(
$c
-> _pow(
$c
-> _copy(
$lower
),
$n
),
$x
);
if
(
$acmp
== 0) {
@$x
=
@$lower
;
return
$x
;
}
$delta
=
$c
-> _mul(
$delta
,
$two
);
}
}
# Use bisection to narrow down the interval.
my
$one
=
$c
-> _one();
{
$delta
=
$c
-> _sub(
$c
-> _copy(
$upper
),
$lower
);
if
(
$c
-> _acmp(
$delta
,
$one
) <= 0) {
@$x
=
@$lower
;
return
$x
;
}
if
(
$DEBUG
) {
"lower = $lower\n"
;
"upper = $upper\n"
;
"delta = $delta\n"
;
"\n"
;
}
$delta
=
$c
-> _div(
$delta
,
$two
);
my
$middle
=
$c
-> _add(
$c
-> _copy(
$lower
),
$delta
);
$acmp
=
$c
-> _acmp(
$c
-> _pow(
$c
-> _copy(
$middle
),
$n
),
$x
);
if
(
$acmp
< 0) {
$lower
=
$middle
;
}
elsif
(
$acmp
> 0) {
$upper
=
$middle
;
}
else
{
@$x
=
@$middle
;
return
$x
;
}
redo
;
}
$x
;
}
##############################################################################
# binary stuff
sub
_and {
my
(
$c
,
$x
,
$y
) =
@_
;
# the shortcut makes equal, large numbers _really_ fast, and makes only a
# very small performance drop for small numbers (e.g. something with less
# than 32 bit) Since we optimize for large numbers, this is enabled.
return
$x
if
$c
->_acmp(
$x
,
$y
) == 0;
# shortcut
my
$m
=
$c
->_one();
my
(
$xr
,
$yr
);
my
$mask
=
$AND_MASK
;
my
$x1
=
$c
->_copy(
$x
);
my
$y1
=
$c
->_copy(
$y
);
my
$z
=
$c
->_zero();
until
(
$c
->_is_zero(
$x1
) ||
$c
->_is_zero(
$y1
)) {
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$mask
);
(
$y1
,
$yr
) =
$c
->_div(
$y1
,
$mask
);
$c
->_add(
$z
,
$c
->_mul([ 0 +
$xr
->[0] & 0 +
$yr
->[0] ],
$m
));
$c
->_mul(
$m
,
$mask
);
}
@$x
=
@$z
;
return
$x
;
}
sub
_xor {
my
(
$c
,
$x
,
$y
) =
@_
;
return
$c
->_zero()
if
$c
->_acmp(
$x
,
$y
) == 0;
# shortcut (see -and)
my
$m
=
$c
->_one();
my
(
$xr
,
$yr
);
my
$mask
=
$XOR_MASK
;
my
$x1
=
$c
->_copy(
$x
);
my
$y1
=
$c
->_copy(
$y
);
# make copy
my
$z
=
$c
->_zero();
until
(
$c
->_is_zero(
$x1
) ||
$c
->_is_zero(
$y1
)) {
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$mask
);
(
$y1
,
$yr
) =
$c
->_div(
$y1
,
$mask
);
# make ints() from $xr, $yr (see _and())
#$b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; }
#$b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; }
#$c->_add($x, $c->_mul($c->_new($xrr ^ $yrr)), $m) );
$c
->_add(
$z
,
$c
->_mul([ 0 +
$xr
->[0] ^ 0 +
$yr
->[0] ],
$m
));
$c
->_mul(
$m
,
$mask
);
}
# the loop stops when the shorter of the two numbers is exhausted
# the remainder of the longer one will survive bit-by-bit, so we simple
# multiply-add it in
$c
->_add(
$z
,
$c
->_mul(
$x1
,
$m
) )
if
!
$c
->_is_zero(
$x1
);
$c
->_add(
$z
,
$c
->_mul(
$y1
,
$m
) )
if
!
$c
->_is_zero(
$y1
);
@$x
=
@$z
;
return
$x
;
}
sub
_or {
my
(
$c
,
$x
,
$y
) =
@_
;
return
$x
if
$c
->_acmp(
$x
,
$y
) == 0;
# shortcut (see _and)
my
$m
=
$c
->_one();
my
(
$xr
,
$yr
);
my
$mask
=
$OR_MASK
;
my
$x1
=
$c
->_copy(
$x
);
my
$y1
=
$c
->_copy(
$y
);
# make copy
my
$z
=
$c
->_zero();
until
(
$c
->_is_zero(
$x1
) ||
$c
->_is_zero(
$y1
)) {
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$mask
);
(
$y1
,
$yr
) =
$c
->_div(
$y1
,
$mask
);
# make ints() from $xr, $yr (see _and())
# $b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; }
# $b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; }
# $c->_add($x, $c->_mul(_new( $c, ($xrr | $yrr) ), $m) );
$c
->_add(
$z
,
$c
->_mul([ 0 +
$xr
->[0] | 0 +
$yr
->[0] ],
$m
));
$c
->_mul(
$m
,
$mask
);
}
# the loop stops when the shorter of the two numbers is exhausted
# the remainder of the longer one will survive bit-by-bit, so we simple
# multiply-add it in
$c
->_add(
$z
,
$c
->_mul(
$x1
,
$m
) )
if
!
$c
->_is_zero(
$x1
);
$c
->_add(
$z
,
$c
->_mul(
$y1
,
$m
) )
if
!
$c
->_is_zero(
$y1
);
@$x
=
@$z
;
return
$x
;
}
sub
_as_hex {
# convert a decimal number to hex (ref to array, return ref to string)
my
(
$c
,
$x
) =
@_
;
return
"0x0"
if
@$x
== 1 &&
$x
->[0] == 0;
my
$x1
=
$c
->_copy(
$x
);
my
$x10000
= [ 0x10000 ];
my
$es
=
''
;
my
$xr
;
until
(
@$x1
== 1 &&
$x1
->[0] == 0) {
# _is_zero()
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$x10000
);
$es
=
sprintf
(
'%04x'
,
$xr
->[0]) .
$es
;
}
#$es = reverse $es;
$es
=~ s/^0*/0x/;
return
$es
;
}
sub
_as_bin {
# convert a decimal number to bin (ref to array, return ref to string)
my
(
$c
,
$x
) =
@_
;
return
"0b0"
if
@$x
== 1 &&
$x
->[0] == 0;
my
$x1
=
$c
->_copy(
$x
);
my
$x10000
= [ 0x10000 ];
my
$es
=
''
;
my
$xr
;
until
(
@$x1
== 1 &&
$x1
->[0] == 0) {
# _is_zero()
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$x10000
);
$es
=
sprintf
(
'%016b'
,
$xr
->[0]) .
$es
;
}
$es
=~ s/^0*/0b/;
return
$es
;
}
sub
_as_oct {
# convert a decimal number to octal (ref to array, return ref to string)
my
(
$c
,
$x
) =
@_
;
return
"00"
if
@$x
== 1 &&
$x
->[0] == 0;
my
$x1
=
$c
->_copy(
$x
);
my
$x1000
= [ 1 << 15 ];
# 15 bits = 32768 = 0100000
my
$es
=
''
;
my
$xr
;
until
(
@$x1
== 1 &&
$x1
->[0] == 0) {
# _is_zero()
(
$x1
,
$xr
) =
$c
->_div(
$x1
,
$x1000
);
$es
=
sprintf
(
"%05o"
,
$xr
->[0]) .
$es
;
}
$es
=~ s/^0*/0/;
# excactly one leading zero
return
$es
;
}
sub
_from_oct {
# convert a octal number to decimal (string, return ref to array)
my
(
$c
,
$os
) =
@_
;
my
$m
=
$c
->_new(1 << 30);
# 30 bits at a time (<32 bits!)
my
$d
= 10;
# 10 octal digits at a time
my
$mul
=
$c
->_one();
my
$x
=
$c
->_zero();
my
$len
=
int
((
length
(
$os
) - 1) /
$d
);
# $d digit parts, w/o the '0'
my
$val
;
my
$i
= -
$d
;
while
(
$len
>= 0) {
$val
=
substr
(
$os
,
$i
,
$d
);
# get oct digits
$val
= CORE::
oct
(
$val
);
$i
-=
$d
;
$len
--;
my
$adder
=
$c
-> _new(
$val
);
$c
->_add(
$x
,
$c
->_mul(
$adder
,
$mul
))
if
$val
!= 0;
$c
->_mul(
$mul
,
$m
)
if
$len
>= 0;
# skip last mul
}
$x
;
}
sub
_from_hex {
# convert a hex number to decimal (string, return ref to array)
my
(
$c
,
$hs
) =
@_
;
my
$m
=
$c
->_new(0x10000000);
# 28 bit at a time (<32 bit!)
my
$d
= 7;
# 7 hexadecimal digits at a time
my
$mul
=
$c
->_one();
my
$x
=
$c
->_zero();
my
$len
=
int
((
length
(
$hs
) - 2) /
$d
);
# $d digit parts, w/o the '0x'
my
$val
;
my
$i
= -
$d
;
while
(
$len
>= 0) {
$val
=
substr
(
$hs
,
$i
,
$d
);
# get hex digits
$val
=~ s/^0x//
if
$len
== 0;
# for last part only because
$val
= CORE::
hex
(
$val
);
# hex does not like wrong chars
$i
-=
$d
;
$len
--;
my
$adder
=
$c
->_new(
$val
);
# if the resulting number was to big to fit into one element, create a
# two-element version (bug found by Mark Lakata - Thanx!)
if
(CORE::
length
(
$val
) >
$BASE_LEN
) {
$adder
=
$c
->_new(
$val
);
}
$c
->_add(
$x
,
$c
->_mul(
$adder
,
$mul
))
if
$val
!= 0;
$c
->_mul(
$mul
,
$m
)
if
$len
>= 0;
# skip last mul
}
$x
;
}
sub
_from_bin {
# convert a hex number to decimal (string, return ref to array)
my
(
$c
,
$bs
) =
@_
;
# instead of converting X (8) bit at a time, it is faster to "convert" the
# number to hex, and then call _from_hex.
my
$hs
=
$bs
;
$hs
=~ s/^[+-]?0b//;
# remove sign and 0b
my
$l
=
length
(
$hs
);
# bits
$hs
=
'0'
x (8 - (
$l
% 8)) .
$hs
if
(
$l
% 8) != 0;
# padd left side w/ 0
my
$h
=
'0x'
.
unpack
(
'H*'
,
pack
(
'B*'
,
$hs
));
# repack as hex
$c
->_from_hex(
$h
);
}
##############################################################################
# special modulus functions
sub
_modinv {
# modular multiplicative inverse
my
(
$c
,
$x
,
$y
) =
@_
;
# modulo zero
if
(
$c
->_is_zero(
$y
)) {
return
;
}
# modulo one
if
(
$c
->_is_one(
$y
)) {
return
$c
->_zero(),
'+'
;
}
my
$u
=
$c
->_zero();
my
$v
=
$c
->_one();
my
$a
=
$c
->_copy(
$y
);
my
$b
=
$c
->_copy(
$x
);
# Euclid's Algorithm for bgcd(), only that we calc bgcd() ($a) and the result
# ($u) at the same time. See comments in BigInt for why this works.
my
$q
;
my
$sign
= 1;
{
(
$a
,
$q
,
$b
) = (
$b
,
$c
->_div(
$a
,
$b
));
# step 1
last
if
$c
->_is_zero(
$b
);
my
$t
=
$c
->_add(
# step 2:
$c
->_mul(
$c
->_copy(
$v
),
$q
),
# t = v * q
$u
);
# + u
$u
=
$v
;
# u = v
$v
=
$t
;
# v = t
$sign
= -
$sign
;
redo
;
}
# if the gcd is not 1, then return NaN
return
unless
$c
->_is_one(
$a
);
(
$v
,
$sign
== 1 ?
'+'
:
'-'
);
}
sub
_modpow {
# modulus of power ($x ** $y) % $z
my
(
$c
,
$num
,
$exp
,
$mod
) =
@_
;
# a^b (mod 1) = 0 for all a and b
if
(
$c
->_is_one(
$mod
)) {
@$num
= 0;
return
$num
;
}
# 0^a (mod m) = 0 if m != 0, a != 0
# 0^0 (mod m) = 1 if m != 0
if
(
$c
->_is_zero(
$num
)) {
if
(
$c
->_is_zero(
$exp
)) {
@$num
= 1;
}
else
{
@$num
= 0;
}
return
$num
;
}
# We could do the following, but it doesn't actually save any time. The
# _copy() is needed in case $num and $mod are the same object.
#$num = $c->_mod($c->_copy($num), $mod);
my
$acc
=
$c
->_copy(
$num
);
my
$t
=
$c
->_one();
my
$expbin
=
$c
->_to_bin(
$exp
);
my
$len
=
length
(
$expbin
);
while
(
$len
--) {
if
(
substr
(
$expbin
,
$len
, 1) eq
'1'
) {
# if odd
$t
=
$c
->_mul(
$t
,
$acc
);
$t
=
$c
->_mod(
$t
,
$mod
);
}
$acc
=
$c
->_mul(
$acc
,
$acc
);
$acc
=
$c
->_mod(
$acc
,
$mod
);
}
@$num
=
@$t
;
$num
;
}
sub
_gcd {
# Greatest common divisor.
my
(
$c
,
$x
,
$y
) =
@_
;
# gcd(0, 0) = 0
# gcd(0, a) = a, if a != 0
if
(
@$x
== 1 &&
$x
->[0] == 0) {
if
(
@$y
== 1 &&
$y
->[0] == 0) {
@$x
= 0;
}
else
{
@$x
=
@$y
;
}
return
$x
;
}
# Until $y is zero ...
until
(
@$y
== 1 &&
$y
->[0] == 0) {
# Compute remainder.
$c
->_mod(
$x
,
$y
);
# Swap $x and $y.
my
$tmp
=
$c
->_copy(
$x
);
@$x
=
@$y
;
$y
=
$tmp
;
# no deref here; that would modify input $y
}
return
$x
;
}
1;
=pod
=head1 NAME
Math::BigInt::Calc - pure Perl module to support Math::BigInt
=head1 SYNOPSIS
# to use it with Math::BigInt
use Math::BigInt lib => 'Calc';
# to use it with Math::BigFloat
use Math::BigFloat lib => 'Calc';
# to use it with Math::BigRat
use Math::BigRat lib => 'Calc';
# explicitly set base length and whether to "use integer"
use Math::BigInt::Calc base_len => 4, use_int => 1;
use Math::BigInt lib => 'Calc';
=head1 DESCRIPTION
Math::BigInt::Calc inherits from Math::BigInt::Lib.
In this library, the numbers are represented interenally in base B = 10**N,
where N is the largest possible integer that does not cause overflow in the
intermediate computations. The base B elements are stored in an array, with the
least significant element stored in array element zero. There are no leading
zero elements, except a single zero element when the number is zero. For
instance, if B = 10000, the number 1234567890 is represented internally as
[7890, 3456, 12].
=head1 OPTIONS
When the module is loaded, it computes the maximum exponent, i.e., power of 10,
that can be used with and without "use integer" in the computations. The default
is to use this maximum exponent. If the combination of the 'base_len' value and
the 'use_int' value exceeds the maximum value, an error is thrown.
=over 4
=item base_len
The base length can be specified explicitly with the 'base_len' option. The
value must be a positive integer.
use Math::BigInt::Calc base_len => 4; # use 10000 as internal base
=item use_int
This option is used to specify whether "use integer" should be used in the
internal computations. The value is interpreted as a boolean value, so use 0 or
"" for false and anything else for true. If the 'base_len' is not specified
together with 'use_int', the current value for the base length is used.
use Math::BigInt::Calc use_int => 1; # use "use integer" internally
=back
=head1 METHODS
This overview constains only the methods that are specific to
C<Math::BigInt::Calc>. For the other methods, see L<Math::BigInt::Lib>.
=over 4
=item _base_len()
Specify the desired base length and whether to enable "use integer" in the
computations.
Math::BigInt::Calc -> _base_len($base_len, $use_int);
Note that it is better to specify the base length and whether to use integers as
options when the module is loaded, for example like this
use Math::BigInt::Calc base_len => 6, use_int => 1;
=back
=head1 SEE ALSO
L<Math::BigInt::Lib> for a description of the API.
Alternative libraries L<Math::BigInt::FastCalc>, L<Math::BigInt::GMP>,
L<Math::BigInt::Pari>, L<Math::BigInt::GMPz>, and L<Math::BigInt::BitVect>.
Some of the modules that use these libraries L<Math::BigInt>,
L<Math::BigFloat>, and L<Math::BigRat>.
=cut