diff options
author | Jarkko Hietaniemi <jhi@iki.fi> | 2001-06-16 14:59:38 +0000 |
---|---|---|
committer | Jarkko Hietaniemi <jhi@iki.fi> | 2001-06-16 14:59:38 +0000 |
commit | 58cde26e9566cfa700a6d18945c4428f22c6be85 (patch) | |
tree | 023954592d91a878566315f910cc9d005e88ae84 /lib | |
parent | 57a31224d60457bbb3416dd1433469e395f1c8f2 (diff) | |
download | perl-58cde26e9566cfa700a6d18945c4428f22c6be85.tar.gz |
Math::BigInt 1.35 from Tels.
p4raw-id: //depot/perl@10628
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Math/BigFloat.pm | 1649 | ||||
-rw-r--r-- | lib/Math/BigInt.pm | 3046 |
2 files changed, 3886 insertions, 809 deletions
diff --git a/lib/Math/BigFloat.pm b/lib/Math/BigFloat.pm index edde97a46d..04622ee423 100644 --- a/lib/Math/BigFloat.pm +++ b/lib/Math/BigFloat.pm @@ -1,434 +1,1387 @@ +#!/usr/bin/perl -w + +# mark.biggar@TrustedSysLabs.com + +# The following hash values are internally used: +# _e: exponent (BigInt) +# _m: mantissa (absolute BigInt) +# sign: +,-,"NaN" if not a number +# _a: accuracy +# _p: precision +# _cow: Copy-On-Write (NRY) + package Math::BigFloat; -use Math::BigInt; +$VERSION = 1.15; +require 5.005; +use Exporter; +use Math::BigInt qw/trace objectify/; +@ISA = qw( Exporter Math::BigInt); +# can not export bneg/babs since the are only in MBI +@EXPORT_OK = qw( + bcmp + badd bmul bdiv bmod bnorm bsub + bgcd blcm bround bfround + bpow bnan bzero bfloor bceil + bacmp bstr binc bdec bint binf + is_odd is_even is_nan is_inf + is_zero is_one sign + ); -use Exporter; # just for use to be happy -@ISA = (Exporter); -$VERSION = '0.03'; +#@EXPORT = qw( ); +use strict; +use vars qw/$AUTOLOAD $accuracy $precision $div_scale $rnd_mode/; +my $class = "Math::BigFloat"; use overload -'+' => sub {new Math::BigFloat &fadd}, -'-' => sub {new Math::BigFloat - $_[2]? fsub($_[1],${$_[0]}) : fsub(${$_[0]},$_[1])}, -'<=>' => sub {$_[2]? fcmp($_[1],${$_[0]}) : fcmp(${$_[0]},$_[1])}, -'cmp' => sub {$_[2]? ($_[1] cmp ${$_[0]}) : (${$_[0]} cmp $_[1])}, -'*' => sub {new Math::BigFloat &fmul}, -'/' => sub {new Math::BigFloat - $_[2]? scalar fdiv($_[1],${$_[0]}) : - scalar fdiv(${$_[0]},$_[1])}, -'%' => sub {new Math::BigFloat - $_[2]? scalar fmod($_[1],${$_[0]}) : - scalar fmod(${$_[0]},$_[1])}, -'neg' => sub {new Math::BigFloat &fneg}, -'abs' => sub {new Math::BigFloat &fabs}, -'int' => sub {new Math::BigInt &f2int}, - -qw( -"" stringify -0+ numify) # Order of arguments unsignificant +'<=>' => sub { + $_[2] ? + $class->bcmp($_[1],$_[0]) : + $class->bcmp($_[0],$_[1])}, +'int' => sub { $_[0]->copy()->bround(0,'trunc'); }, ; -sub new { - my ($class) = shift; - my ($foo) = fnorm(shift); - bless \$foo, $class; +# are NaNs ok? +my $NaNOK=1; +# set to 1 for tracing +my $trace = 0; +# constant for easier life +my $nan = 'NaN'; +my $ten = Math::BigInt->new(10); # shortcut for speed + +# Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc' +$rnd_mode = 'even'; +$accuracy = undef; +$precision = undef; +$div_scale = 40; + +{ + # checks for AUTOLOAD + my %methods = map { $_ => 1 } + qw / fadd fsub fmul fdiv fround ffround fsqrt fmod fstr fsstr fpow fnorm + fabs fneg fint fcmp fzero fnan finc fdec + /; + + sub method_valid { return exists $methods{$_[0]||''}; } } -sub numify { 0 + "${$_[0]}" } # Not needed, additional overhead - # comparing to direct compilation based on - # stringify -sub stringify { - my $n = ${$_[0]}; +############################################################################## +# constructors - my $minus = ($n =~ s/^([+-])// && $1 eq '-'); - $n =~ s/E//; +sub new + { + # create a new BigFloat object from a string or another bigfloat object. + # _e: exponent + # _m: mantissa + # sign => sign (+/-), or "NaN" - $n =~ s/([-+]\d+)$//; + trace (@_); + my $class = shift; + + my $wanted = shift; # avoid numify call by not using || here + return $class->bzero() if !defined $wanted; # default to 0 + return $wanted->copy() if ref($wanted) eq $class; - my $e = $1; - my $ln = length($n); + my $round = shift; $round = 0 if !defined $round; # no rounding as default + my $self = {}; bless $self, $class; + #shortcut for bigints + if (ref($wanted) eq 'Math::BigInt') + { + $self->{_m} = $wanted; + $self->{_e} = Math::BigInt->new(0); + $self->{_m}->babs(); + $self->{sign} = $wanted->sign(); + return $self; + } + # got string + # handle '+inf', '-inf' first + if ($wanted =~ /^[+-]inf$/) + { + $self->{_e} = Math::BigInt->new(0); + $self->{_m} = Math::BigInt->new(0); + $self->{sign} = $wanted; + return $self; + } + #print "new string '$wanted'\n"; + my ($mis,$miv,$mfv,$es,$ev) = Math::BigInt::_split(\$wanted); + if (!ref $mis) + { + die "$wanted is not a number initialized to $class" if !$NaNOK; + $self->{_e} = Math::BigInt->new(0); + $self->{_m} = Math::BigInt->new(0); + $self->{sign} = $nan; + } + else + { + # make integer from mantissa by adjusting exp, then convert to bigint + $self->{_e} = Math::BigInt->new("$$es$$ev"); # exponent + $self->{_m} = Math::BigInt->new("$$mis$$miv$$mfv"); # create mantissa + # 3.123E0 = 3123E-3, and 3.123E-2 => 3123E-5 + $self->{_e} -= CORE::length($$mfv); + $self->{sign} = $self->{_m}->sign(); $self->{_m}->babs(); + } + #print "$wanted => $self->{sign} $self->{value}->[0]\n"; + $self->bnorm(); # first normalize + # if any of the globals is set, round to them and thus store them insid $self + $self->round($accuracy,$precision,$rnd_mode) + if defined $accuracy || defined $precision; + return $self; + } - if ( defined $e ) +# some shortcuts for easier life +sub bfloat + { + # exportable version of new + trace(@_); + return $class->new(@_); + } + +sub bint + { + # exportable version of new + trace(@_); + return $class->new(@_,0)->bround(0,'trunc'); + } + +sub bnan + { + # create a bigfloat 'NaN', if given a BigFloat, set it to 'NaN' + my $self = shift; + $self = $class if !defined $self; + if (!ref($self)) { - if ($e > 0) { - $n .= "0" x $e . '.'; - } elsif (abs($e) < $ln) { - substr($n, $ln + $e, 0) = '.'; - } else { - $n = '.' . ("0" x (abs($e) - $ln)) . $n; - } + my $c = $self; $self = {}; bless $self, $c; } - $n = "-$n" if $minus; + $self->{_e} = new Math::BigInt 0; + $self->{_m} = new Math::BigInt 0; + $self->{sign} = $nan; + trace('NaN'); + return $self; + } - # 1 while $n =~ s/(.*\d)(\d\d\d)/$1,$2/; +sub binf + { + # create a bigfloat '+-inf', if given a BigFloat, set it to '+-inf' + my $self = shift; + my $sign = shift; $sign = '+' if !defined $sign || $sign ne '-'; - return $n; -} + $self = $class if !defined $self; + if (!ref($self)) + { + my $c = $self; $self = {}; bless $self, $c; + } + $self->{_e} = new Math::BigInt 0; + $self->{_m} = new Math::BigInt 0; + $self->{sign} = $sign.'inf'; + trace('inf'); + return $self; + } -sub import { - shift; - return unless @_; - if (@_ == 1 && $_[0] ne ':constant') { - if ($_[0] > 0) { - if ($VERSION < $_[0]) { - die __PACKAGE__.": $_[0] required--this is only version $VERSION"; +sub bzero + { + # create a bigfloat '+0', if given a BigFloat, set it to 0 + my $self = shift; + $self = $class if !defined $self; + if (!ref($self)) + { + my $c = $self; $self = {}; bless $self, $c; + } + $self->{_m} = new Math::BigInt 0; + $self->{_e} = new Math::BigInt 1; + $self->{sign} = '+'; + trace('0'); + return $self; + } + +############################################################################## +# string conversation + +sub bstr + { + # (ref to BFLOAT or num_str ) return num_str + # Convert number from internal format to (non-scientific) string format. + # internal format is always normalized (no leading zeros, "-0" => "+0") + trace(@_); + my ($self,$x) = objectify(1,@_); + + #return "Oups! e was $nan" if $x->{_e}->{sign} eq $nan; + #return "Oups! m was $nan" if $x->{_m}->{sign} eq $nan; + return $x->{sign} if $x->{sign} !~ /^[+-]$/; + return '0' if $x->is_zero(); + + my $es = $x->{_m}->bstr(); + if ($x->{_e}->is_zero()) + { + $es = $x->{sign}.$es if $x->{sign} eq '-'; + return $es; + } + + if ($x->{_e}->sign() eq '-') + { + if ($x->{_e} <= -CORE::length($es)) + { + # print "style: 0.xxxx\n"; + my $r = $x->{_e}->copy(); $r->babs()->bsub( CORE::length($es) ); + $es = '0.'. ('0' x $r) . $es; + } + else + { + # print "insert '.' at $x->{_e} in '$es'\n"; + substr($es,$x->{_e},0) = '.'; } - } else { - die __PACKAGE__.": unknown import: $_[0]"; } + else + { + # expand with zeros + $es .= '0' x $x->{_e}; + } + $es = $x->{sign}.$es if $x->{sign} eq '-'; + return $es; } - overload::constant float => sub {Math::BigFloat->new(shift)}; -} -$div_scale = 40; +sub bsstr + { + # (ref to BFLOAT or num_str ) return num_str + # Convert number from internal format to scientific string format. + # internal format is always normalized (no leading zeros, "-0E0" => "+0E0") + trace(@_); + my ($self,$x) = objectify(1,@_); -# Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'. + return "Oups! e was $nan" if $x->{_e}->{sign} eq $nan; + return "Oups! m was $nan" if $x->{_m}->{sign} eq $nan; + return $x->{sign} if $x->{sign} !~ /^[+-]$/; + my $sign = $x->{_e}->{sign}; $sign = '' if $sign eq '-'; + my $sep = 'e'.$sign; + return $x->{_m}->bstr().$sep.$x->{_e}->bstr(); + } + +sub numify + { + # Make a number from a BigFloat object + # simple return string and let Perl's atoi() handle the rest + trace (@_); + my ($self,$x) = objectify(1,@_); + return $x->bsstr(); + } -$rnd_mode = 'even'; +############################################################################## +# public stuff (usually prefixed with "b") + +# really? Just for exporting them is not what I had in mind +#sub babs +# { +# $class->SUPER::babs($class,@_); +# } +#sub bneg +# { +# $class->SUPER::bneg($class,@_); +# } +#sub bnot +# { +# $class->SUPER::bnot($class,@_); +# } + +sub bcmp + { + # Compares 2 values. Returns one of undef, <0, =0, >0. (suitable for sort) + # (BFLOAT or num_str, BFLOAT or num_str) return cond_code + my ($self,$x,$y) = objectify(2,@_); + return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + # check sign + return 1 if $x->{sign} eq '+' && $y->{sign} eq '-'; + return -1 if $x->{sign} eq '-' && $y->{sign} eq '+'; # does also -x <=> 0 + + return 0 if $x->is_zero() && $y->is_zero(); # 0 <=> 0 + return -1 if $x->is_zero() && $y->{sign} eq '+'; # 0 <=> +y + return 1 if $y->is_zero() && $x->{sign} eq '+'; # +x <=> 0 + + # adjust so that exponents are equal + my $lx = $x->{_m}->length() + $x->{_e}; + my $ly = $y->{_m}->length() + $y->{_e}; + # print "x $x y $y lx $lx ly $ly\n"; + my $l = $lx - $ly; $l = -$l if $x->{sign} eq '-'; + # print "$l $x->{sign}\n"; + return $l if $l != 0; + + # lens are equal, so compare mantissa, if equal, compare exponents + # this assumes normaized numbers (no trailing zeros etc) + my $rc = $x->{_m} <=> $y->{_m} || $x->{_e} <=> $y->{_e}; + $rc = -$rc if $x->{sign} eq '-'; # -124 < -123 + return $rc; + } + +sub bacmp + { + # Compares 2 values, ignoring their signs. + # Returns one of undef, <0, =0, >0. (suitable for sort) + # (BFLOAT or num_str, BFLOAT or num_str) return cond_code + my ($self,$x,$y) = objectify(2,@_); + return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + # signs are ignored, so check length + # length(x) is length(m)+e aka length of non-fraction part + # the longer one is bigger + my $l = $x->length() - $y->length(); + #print "$l\n"; + return $l if $l != 0; + #print "equal lengths\n"; + + # if both are equal long, make full compare + # first compare only the mantissa + # if mantissa are equal, compare fractions + + return $x->{_m} <=> $y->{_m} || $x->{_e} <=> $y->{_e}; + } -sub fadd; sub fsub; sub fmul; sub fdiv; -sub fneg; sub fabs; sub fcmp; -sub fround; sub ffround; -sub fnorm; sub fsqrt; - -# Convert a number to canonical string form. -# Takes something that looks like a number and converts it to -# the form /^[+-]\d+E[+-]\d+$/. -sub fnorm { #(string) return fnum_str - local($_) = @_; - s/\s+//g; # strip white space - no warnings; # $4 and $5 below might legitimately be undefined - if (/^([+-]?)(\d*)(\.(\d*))?([Ee]([+-]?\d+))?$/ && "$2$4" ne '') { - &norm(($1 ? "$1$2$4" : "+$2$4"),(($4 ne '') ? $6-length($4) : $6)); - } else { - 'NaN'; +sub badd + { + # add second arg (BFLOAT or string) to first (BFLOAT) (modifies first) + # return result as BFLOAT + trace(@_); + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + #print "add $x ",ref($x)," $y ",ref($y),"\n"; + return $x->bnan() if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + # speed: no add for 0+y or x+0 + return $x if $y->is_zero(); # x+0 + if ($x->is_zero()) # 0+y + { + # make copy, clobbering up x (modify in place!) + $x->{_e} = $y->{_e}->copy(); + $x->{_m} = $y->{_m}->copy(); + $x->{sign} = $y->{sign} || $nan; + return $x->round($a,$p,$r,$y); } -} + + # take lower of the two e's and adapt m1 to it to match m2 + my $e = $y->{_e}; $e = Math::BigInt::bzero() if !defined $e; # if no BFLOAT + $e = $e - $x->{_e}; + my $add = $y->{_m}->copy(); + if ($e < 0) + { + #print "e < 0\n"; + #print "\$x->{_m}: $x->{_m} "; + #print "\$x->{_e}: $x->{_e}\n"; + my $e1 = $e->copy()->babs(); + $x->{_m} *= (10 ** $e1); + $x->{_e} += $e; # need the sign of e + #$x->{_m} += $y->{_m}; + #print "\$x->{_m}: $x->{_m} "; + #print "\$x->{_e}: $x->{_e}\n"; + } + elsif ($e > 0) + { + #print "e > 0\n"; + #print "\$x->{_m}: $x->{_m} \$y->{_m}: $y->{_m} \$e: $e ",ref($e),"\n"; + $add *= (10 ** $e); + #$x->{_m} += $y->{_m} * (10 ** $e); + #print "\$x->{_m}: $x->{_m}\n"; + } + # else: both e are same, so leave them + #print "badd $x->{sign}$x->{_m} + $y->{sign}$add\n"; + # fiddle with signs + $x->{_m}->{sign} = $x->{sign}; + $add->{sign} = $y->{sign}; + # finally do add/sub + $x->{_m} += $add; + # re-adjust signs + $x->{sign} = $x->{_m}->{sign}; + $x->{_m}->{sign} = '+'; + return $x->round($a,$p,$r,$y); + } + +sub bsub + { + # (BINT or num_str, BINT or num_str) return num_str + # subtract second arg from first, modify first + my ($self,$x,$y) = objectify(2,@_); -# normalize number -- for internal use -sub norm { #(mantissa, exponent) return fnum_str - local($_, $exp) = @_; - $exp = 0 unless defined $exp; - if ($_ eq 'NaN') { - 'NaN'; - } else { - s/^([+-])0+/$1/; # strip leading zeros - if (length($_) == 1) { - '+0E+0'; - } else { - $exp += length($1) if (s/(0+)$//); # strip trailing zeros - sprintf("%sE%+ld", $_, $exp); - } + trace(@_); + $x->badd($y->bneg()); # badd does not leave internal zeros + $y->bneg(); # refix y, assumes no one reads $y in between + return $x; + } + +sub binc + { + # increment arg by one + my ($self,$x,$a,$p,$r) = objectify(1,@_); + trace(@_); + $x->badd($self->_one())->round($a,$p,$r); + } + +sub bdec + { + # decrement arg by one + my ($self,$x,$a,$p,$r) = objectify(1,@_); + trace(@_); + $x->badd($self->_one('-'))->round($a,$p,$r); + } + +sub blcm + { + # (BINT or num_str, BINT or num_str) return BINT + # does not modify arguments, but returns new object + # Lowest Common Multiplicator + trace(@_); + + my ($self,@arg) = objectify(0,@_); + my $x = $self->new(shift @arg); + while (@arg) { $x = _lcm($x,shift @arg); } + $x; + } + +sub bgcd + { + # (BINT or num_str, BINT or num_str) return BINT + # does not modify arguments, but returns new object + # GCD -- Euclids algorithm Knuth Vol 2 pg 296 + trace(@_); + + my ($self,@arg) = objectify(0,@_); + my $x = $self->new(shift @arg); + while (@arg) { $x = _gcd($x,shift @arg); } + $x; + } + +sub is_zero + { + # return true if arg (BINT or num_str) is zero (array '+', '0') + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + trace(@_); + return ($x->{sign} ne $nan && $x->{_m}->is_zero()); + } + +sub is_one + { + # return true if arg (BINT or num_str) is +1 (array '+', '1') + # or -1 if signis given + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + my $sign = $_[2] || '+'; + return ($x->{sign} eq $sign && $x->{_e}->is_zero() && $x->{_m}->is_one()); + } + +sub is_odd + { + # return true if arg (BINT or num_str) is odd or -1 if even + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + return ($x->{sign} ne $nan && $x->{_e}->is_zero() && $x->{_m}->is_odd()); + } + +sub is_even + { + # return true if arg (BINT or num_str) is even or -1 if odd + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + return 0 if $x->{sign} eq $nan; # NaN isn't + return 1 if $x->{_m}->is_zero(); # 0 is + return ($x->{_e}->is_zero() && $x->{_m}->is_even()); + } + +sub bmul + { + # multiply two numbers -- stolen from Knuth Vol 2 pg 233 + # (BINT or num_str, BINT or num_str) return BINT + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + # trace(@_); + + #print "mul $x->{_m}e$x->{_e} $y->{_m}e$y->{_e}\n"; + return $x->bnan() if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + # print "$x $y\n"; + # aEb * cEd = (a*c)E(b+d) + $x->{_m} = $x->{_m} * $y->{_m}; + #print "m: $x->{_m}\n"; + $x->{_e} = $x->{_e} + $y->{_e}; + #print "e: $x->{_m}\n"; + # adjust sign: + $x->{sign} = $x->{sign} ne $y->{sign} ? '-' : '+'; + #print "s: $x->{sign}\n"; + return $x->round($a,$p,$r,$y); + } + +sub bdiv + { + # (dividend: BFLOAT or num_str, divisor: BFLOAT or num_str) return + # (BFLOAT,BFLOAT) (quo,rem) or BINT (only rem) + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + return wantarray ? ($x->bnan(),bnan()) : $x->bnan() + if ($x->{sign} eq $nan || $y->is_nan() || $y->is_zero()); + + # we need to limit the accuracy to protect against overflow + my ($scale,$mode) = $x->_scale_a($accuracy,$rnd_mode,$a,$r); # ignore $p + my $add = 1; # for proper rounding + my $fallback = 0; + if (!defined $scale) + { + $fallback = 1; $scale = $div_scale; # simulate old behaviour } -} + #print "div_scale $div_scale\n"; + my $lx = $x->{_m}->length(); + $scale = $lx if $lx > $scale; + my $ly = $y->{_m}->length(); + $scale = $ly if $ly > $scale; + #print "scale $scale $lx $ly\n"; + #$scale = $scale - $lx + $ly; + #print "scale $scale\n"; + $scale += $add; # calculate some more digits for proper rounding -# negation -sub fneg { #(fnum_str) return fnum_str - local($_) = fnorm($_[$[]); - vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0E+0'; # flip sign - s/^H/N/; - $_; -} + # print "bdiv $x $y scale $scale xl $lx yl $ly\n"; -# absolute value -sub fabs { #(fnum_str) return fnum_str - local($_) = fnorm($_[$[]); - s/^-/+/; # mash sign - $_; -} + return wantarray ? ($x,$self->bzero()) : $x if $x->is_zero(); + + $x->{sign} = $x->{sign} ne $y->sign() ? '-' : '+'; -# multiplication -sub fmul { #(fnum_str, fnum_str) return fnum_str - local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1])); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - local($xm,$xe) = split('E',$x); - local($ym,$ye) = split('E',$y); - &norm(Math::BigInt::bmul($xm,$ym),$xe+$ye); + # check for / +-1 ( +/- 1E0) + if ($y->is_one()) + { + return wantarray ? ($x,$self->bzero()) : $x; } -} -# addition -sub fadd { #(fnum_str, fnum_str) return fnum_str - local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1])); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - local($xm,$xe) = split('E',$x); - local($ym,$ye) = split('E',$y); - ($xm,$xe,$ym,$ye) = ($ym,$ye,$xm,$xe) if ($xe < $ye); - &norm(Math::BigInt::badd($ym,$xm.('0' x ($xe-$ye))),$ye); + # a * 10 ** b / c * 10 ** d => a/c * 10 ** (b-d) + #print "self: $self x: $x ref(x) ", ref($x)," m: $x->{_m}\n"; + # my $scale_10 = 10 ** $scale; $x->{_m}->bmul($scale_10); + $x->{_m}->blsft($scale,10); + #print "m: $x->{_m}\n"; + $x->{_m}->bdiv( $y->{_m} ); # a/c + #print "m: $x->{_m}\n"; + #print "e: $x->{_e} $y->{_e}",$scale,"\n"; + $x->{_e}->bsub($y->{_e}); # b-d + #print "e: $x->{_e}\n"; + $x->{_e}->bsub($scale); # correct for 10**scale + #print "e: $x->{_e}\n"; + $x->bnorm(); # remove trailing zeros + + # print "round $x to -$scale (-$add) mode $mode\n"; + #print "$x ",scalar ref($x), "=> $t",scalar ref($t),"\n"; + if ($fallback) + { + $scale -= $add; $x->round($scale,undef,$r); # round to less } -} + else + { + return $x->round($a,$p,$r,$y); + } + if (wantarray) + { + my $rem = $x->copy(); + $rem->bmod($y,$a,$p,$r); + return ($x,$rem->round($scale,undef,$r)) if $fallback; + return ($x,$rem->round($a,$p,$r,$y)); + } + return $x; + } -# subtraction -sub fsub { #(fnum_str, fnum_str) return fnum_str - fadd($_[$[],fneg($_[$[+1])); -} +sub bmod + { + # (dividend: BFLOAT or num_str, divisor: BFLOAT or num_str) return reminder + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); -# division -# args are dividend, divisor, scale (optional) -# result has at most max(scale, length(dividend), length(divisor)) digits -sub fdiv #(fnum_str, fnum_str[,scale]) return fnum_str -{ - local($x,$y,$scale) = (fnorm($_[$[]),fnorm($_[$[+1]),$_[$[+2]); - if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') { - 'NaN'; - } else { - local($xm,$xe) = split('E',$x); - local($ym,$ye) = split('E',$y); - $scale = $div_scale if (!$scale); - $scale = length($xm)-1 if (length($xm)-1 > $scale); - $scale = length($ym)-1 if (length($ym)-1 > $scale); - $scale = $scale + length($ym) - length($xm); - &norm(&round(Math::BigInt::bdiv($xm.('0' x $scale),$ym), - Math::BigInt::babs($ym)), - $xe-$ye-$scale); + return $x->bnan() if ($x->{sign} eq $nan || $y->is_nan() || $y->is_zero()); + return $x->bzero() if $y->is_one(); + + # XXX tels: not done yet + return $x->round($a,$p,$r,$y); + } + +sub bsqrt + { + # calculate square root + # this should use a different test to see wether the accuracy we want is... + my ($self,$x,$a,$p,$r) = objectify(1,@_); + + # we need to limit the accuracy to protect against overflow + my ($scale,$mode) = $x->_scale_a($accuracy,$rnd_mode,$a,$r); # ignore $p + $scale = $div_scale if (!defined $scale); # simulate old behaviour + # print "scale $scale\n"; + + return $x->bnan() if ($x->sign() eq '-') || ($x->sign() eq $nan); + return $x if $x->is_zero() || $x == 1; + + my $len = $x->{_m}->length(); + $scale = $len if $scale < $len; + print "scale $scale\n"; + $scale += 1; # because we need more than $scale to later round + my $e = Math::BigFloat->new("1E-$scale"); # make test variable + return $x->bnan() if $e->sign() eq 'NaN'; + + # print "$scale $e\n"; + + my $gs = Math::BigFloat->new(100); # first guess + my $org = $x->copy(); + + # start with some reasonable guess + #$x *= 10 ** ($len - $org->{_e}); + #$x /= 2; + #my $gs = Math::BigFloat->new(1); + # print "first guess: $gs (x $x)\n"; + + my $diff = $e; + my $y = $x->copy(); + my $two = Math::BigFloat->new(2); + $x = Math::BigFloat->new($x) if ref($x) ne $class; # promote BigInts + # $scale = 2; + while ($diff >= $e) + { + #sleep(1); + return $x->bnan() if $gs->is_zero(); + #my $r = $y / $gs; + #print "$y / $gs = ",$r," ref(\$r) ",ref($r),"\n"; + my $r = $y->copy(); $r->bdiv($gs,$scale); # $scale); + $x = ($r + $gs); + $x->bdiv($two,$scale); # $scale *= 2; + $diff = $x->copy()->bsub($gs)->babs(); + #print "gs: $gs x: $x \n"; + $gs = $x->copy(); + # print "$x $org $scale $gs\n"; + #$gs *= 2; + #$y = $org->copy(); + #$x += $y->bdiv($x, $scale); # need only $gs scale + # $y = $org->copy(); + #$x /= 2; + print "x $x diff $diff $e\n"; } -} + $x->bnorm($scale-1,undef,$mode); + } -# modular division -# args are dividend, divisor -sub fmod #(fnum_str, fnum_str) return fnum_str -{ - local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1])); - if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') { - 'NaN'; - } else { - local($xm,$xe) = split('E',$x); - local($ym,$ye) = split('E',$y); - if ( $xe < $ye ) - { - $ym .= ('0' x ($ye-$xe)); - } - else - { - $xm .= ('0' x ($xe-$ye)); - } - &norm(Math::BigInt::bmod($xm,$ym)); +sub _set + { + # set to a specific 'small' value, internal usage + my $x = shift; + my $v = shift||0; + + $x->{sign} = $nan, return if $v !~ /^[-+]?[0-9]+$/; + $x->{_m}->{value} = [abs($v)]; + $x->{_e}->{value} = [0]; + $x->{sign} = '+'; $x->{sign} = '-' if $v < 0; + return $x; + } + +sub bpow + { + # (BFLOAT or num_str, BFLOAT or num_str) return BFLOAT + # compute power of two numbers, second arg is used as integer + # modifies first argument + + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + return $x->bnan() if $x->{sign} eq $nan || $y->{sign} eq $nan; + return $x->_one() if $y->is_zero(); + return $x if $x->is_one() || $y->is_one(); + my $y1 = $y->as_number(); # make bigint + if ($x == -1) + { + # if $x == -1 and odd/even y => +1/-1 because +-1 ^ (+-1) => +-1 + return $y1->is_odd() ? $x : $x->_set(1); # $x->babs() would work to } -} -# round int $q based on fraction $r/$base using $rnd_mode -sub round { #(int_str, int_str, int_str) return int_str - local($q,$r,$base) = @_; - if ($q eq 'NaN' || $r eq 'NaN') { - 'NaN'; - } elsif ($rnd_mode eq 'trunc') { - $q; # just truncate - } else { - local($cmp) = Math::BigInt::bcmp(Math::BigInt::bmul($r,'+2'),$base); - if ( $cmp < 0 || - ($cmp == 0 && ( - ($rnd_mode eq 'zero' ) || - ($rnd_mode eq '-inf' && (substr($q,$[,1) eq '+')) || - ($rnd_mode eq '+inf' && (substr($q,$[,1) eq '-')) || - ($rnd_mode eq 'even' && $q =~ /[24680]$/ ) || - ($rnd_mode eq 'odd' && $q =~ /[13579]$/ ) ) - ) - ) { - $q; # round down - } else { - Math::BigInt::badd($q, ((substr($q,$[,1) eq '-') ? '-1' : '+1')); - # round up - } + return $x if $x->is_zero() && $y->{sign} eq '+'; # 0**y => 0 (if not y <= 0) + # 0 ** -y => 1 / (0 ** y) => / 0! + return $x->bnan() if $x->is_zero() && $y->{sign} eq '-'; + + # calculate $x->{_m} ** $y and $x->{_e} * $y separately (faster) + $y1->babs(); + $x->{_m}->bpow($y1); + $x->{_e}->bmul($y1); + $x->{sign} = $nan if $x->{_m}->{sign} eq $nan || $x->{_e}->{sign} eq $nan; + $x->bnorm(); + if ($y->{sign} eq '-') + { + # modify $x in place! + my $z = $x->copy(); $x->_set(1); + return $x->bdiv($z,$a,$p,$r); # round in one go (might ignore y's A!) } -} + return $x->round($a,$p,$r,$y); + } + +############################################################################### +# rounding functions + +sub bfround + { + # precision: round to the $Nth digit left (+$n) or right (-$n) from the '.' + # $n == 0 means round to integer + # expects and returns normalized numbers! + my $x = shift; $x = $class->new($x) unless ref $x; -# round the mantissa of $x to $scale digits -sub fround { #(fnum_str, scale) return fnum_str - local($x,$scale) = (fnorm($_[$[]),$_[$[+1]); - if ($x eq 'NaN' || $scale <= 0) { - $x; - } else { - local($xm,$xe) = split('E',$x); - if (length($xm)-1 <= $scale) { - $x; - } else { - &norm(&round(substr($xm,$[,$scale+1), - "+0".substr($xm,$[+$scale+1),"+1"."0" x length(substr($xm,$[+$scale+1))), - $xe+length($xm)-$scale-1); - } + return $x if $x->modify('bfround'); + + my ($scale,$mode) = $x->_scale_p($precision,$rnd_mode,@_); + return $x if !defined $scale; # no-op + + # print "MBF bfround $x to scale $scale mode $mode\n"; + return $x if $x->is_nan() or $x->is_zero(); + + if ($scale < 0) + { + # print "bfround scale $scale e $x->{_e}\n"; + # round right from the '.' + return $x if $x->{_e} >= 0; # nothing to round + $scale = -$scale; # positive for simplicity + my $len = $x->{_m}->length(); # length of mantissa + my $dad = -$x->{_e}; # digits after dot + my $zad = 0; # zeros after dot + $zad = -$len-$x->{_e} if ($x->{_e} < -$len);# for 0.00..00xxx style + # print "scale $scale dad $dad zad $zad len $len\n"; + + # number bsstr len zad dad + # 0.123 123e-3 3 0 3 + # 0.0123 123e-4 3 1 4 + # 0.001 1e-3 1 2 3 + # 1.23 123e-2 3 0 2 + # 1.2345 12345e-4 5 0 4 + + # do not round after/right of the $dad + return $x if $scale > $dad; # 0.123, scale >= 3 => exit + + # round to zero if rounding inside the $zad, but not for last zero like: + # 0.0065, scale -2, round last '0' with following '65' (scale == zad case) + if ($scale < $zad) + { + $x->{_m} = Math::BigInt->new(0); + $x->{_e} = Math::BigInt->new(1); + $x->{sign} = '+'; + return $x; + } + if ($scale == $zad) # for 0.006, scale -2 and trunc + { + $scale = -$len; + } + else + { + # adjust round-point to be inside mantissa + if ($zad != 0) + { + $scale = $scale-$zad; + } + else + { + my $dbd = $len - $dad; $dbd = 0 if $dbd < 0; # digits before dot + $scale = $dbd+$scale; + } + } + # print "round to $x->{_m} to $scale\n"; } -} + else + { + # 123 => 100 means length(123) = 3 - $scale (2) => 1 -# round $x at the 10 to the $scale digit place -sub ffround { #(fnum_str, scale) return fnum_str - local($x,$scale) = (fnorm($_[$[]),$_[$[+1]); - if ($x eq 'NaN') { - 'NaN'; - } else { - local($xm,$xe) = split('E',$x); - if ($xe >= $scale) { - $x; - } else { - $xe = length($xm)+$xe-$scale; - if ($xe < 1) { - '+0E+0'; - } elsif ($xe == 1) { - # The first substr preserves the sign, passing a non- - # normalized "-0" to &round when rounding -0.006 (for - # example), purely so &round won't lose the sign. - &norm(&round(substr($xm,$[,1).'0', - "+0".substr($xm,$[+1), - "+1"."0" x length(substr($xm,$[+1))), $scale); - } else { - &norm(&round(substr($xm,$[,$xe), - "+0".substr($xm,$[+$xe), - "+1"."0" x length(substr($xm,$[+$xe))), $scale); - } - } + # calculate digits before dot + my $dbt = $x->{_m}->length(); $dbt += $x->{_e} if $x->{_e}->sign() eq '-'; + if (($scale > $dbt) && ($dbt < 0)) + { + # if not enough digits before dot, round to zero + $x->{_m} = Math::BigInt->new(0); + $x->{_e} = Math::BigInt->new(1); + $x->{sign} = '+'; + return $x; + } + if (($scale >= 0) && ($dbt == 0)) + { + # 0.49->bfround(1): scale == 1, dbt == 0: => 0.0 + # 0.51->bfround(0): scale == 0, dbt == 0: => 1.0 + # 0.5->bfround(0): scale == 0, dbt == 0: => 0 + # 0.05->bfround(0): scale == 0, dbt == 0: => 0 + # print "$scale $dbt $x->{_m}\n"; + $scale = -$x->{_m}->length(); + } + elsif ($dbt > 0) + { + # correct by subtracting scale + $scale = $dbt - $scale; + } + else + { + $scale = $x->{_m}->length() - $scale; + } } -} + #print "using $scale for $x->{_m} with '$mode'\n"; + # pass sign to bround for '+inf' and '-inf' rounding modes + $x->{_m}->{sign} = $x->{sign}; + $x->{_m}->bround($scale,$mode); + $x->{_m}->{sign} = '+'; # fix sign back + $x->bnorm(); + } + +sub bround + { + # accuracy: preserve $N digits, and overwrite the rest with 0's + my $x = shift; $x = $class->new($x) unless ref $x; + my ($scale,$mode) = $x->_scale_a($accuracy,$rnd_mode,@_); + return $x if !defined $scale; # no-op + + return $x if $x->modify('bround'); + + # print "bround $scale $mode\n"; + # 0 => return all digits, scale < 0 makes no sense + return $x if ($scale <= 0); + return $x if $x->is_nan() or $x->is_zero(); # never round a 0 + + # if $e longer than $m, we have 0.0000xxxyyy style number, and must + # subtract the delta from scale, to simulate keeping the zeros + # -5 +5 => 1; -10 +5 => -4 + my $delta = $x->{_e} + $x->{_m}->length() + 1; + # removed by tlr, since causes problems with fraction tests: + # $scale += $delta if $delta < 0; + + # if we should keep more digits than the mantissa has, do nothing + return $x if $x->{_m}->length() <= $scale; -# Calculate the integer part of $x -sub f2int { #(fnum_str) return inum_str - local($x) = ${$_[$[]}; - if ($x eq 'NaN') { - die "Attempt to take int(NaN)"; - } else { - local($xm,$xe) = split('E',$x); - if ($xe >= 0) { - $xm . '0' x $xe; - } else { - $xe = length($xm)+$xe; - if ($xe <= 1) { - '+0'; - } else { - substr($xm,$[,$xe); - } - } + # pass sign to bround for '+inf' and '-inf' rounding modes + $x->{_m}->{sign} = $x->{sign}; + $x->{_m}->bround($scale,$mode); # round mantissa + $x->{_m}->{sign} = '+'; # fix sign back + return $x->bnorm(); # del trailing zeros gen. by bround() + } + +sub bfloor + { + # return integer less or equal then $x + my ($self,$x,$a,$p,$r) = objectify(1,@_); + + return $x if $x->modify('bfloor'); + + return $x if $x->{sign} !~ /^[+-]$/; # nan, +inf, -inf + + # if $x has digits after dot + if ($x->{_e}->{sign} eq '-') + { + $x->{_m}->brsft(-$x->{_e},10); + $x->{_e}->bzero(); + $x-- if $x->{sign} eq '-'; } -} + return $x->round($a,$p,$r); + } -# compare 2 values returns one of undef, <0, =0, >0 -# returns undef if either or both input value are not numbers -sub fcmp #(fnum_str, fnum_str) return cond_code -{ - local($x, $y) = (fnorm($_[$[]),fnorm($_[$[+1])); - if ($x eq "NaN" || $y eq "NaN") { - undef; - } else { - local($xm,$xe,$ym,$ye) = split('E', $x."E$y"); - if ($xm eq '+0' || $ym eq '+0') { - return $xm <=> $ym; - } - if ( $xe < $ye ) # adjust the exponents to be equal - { - $ym .= '0' x ($ye - $xe); - $ye = $xe; - } - elsif ( $ye < $xe ) # same here - { - $xm .= '0' x ($xe - $ye); - $xe = $ye; - } - return Math::BigInt::cmp($xm,$ym); +sub bceil + { + # return integer greater or equal then $x + my ($self,$x,$a,$p,$r) = objectify(1,@_); + + return $x if $x->modify('bceil'); + return $x if $x->{sign} !~ /^[+-]$/; # nan, +inf, -inf + + # if $x has digits after dot + if ($x->{_e}->{sign} eq '-') + { + $x->{_m}->brsft(-$x->{_e},10); + $x->{_e}->bzero(); + $x++ if $x->{sign} eq '+'; } -} + return $x->round($a,$p,$r); + } + +############################################################################### -# square root by Newtons method. -sub fsqrt { #(fnum_str[, scale]) return fnum_str - local($x, $scale) = (fnorm($_[$[]), $_[$[+1]); - if ($x eq 'NaN' || $x =~ /^-/) { - 'NaN'; - } elsif ($x eq '+0E+0') { - '+0E+0'; - } else { - local($xm, $xe) = split('E',$x); - $scale = $div_scale if (!$scale); - $scale = length($xm)-1 if ($scale < length($xm)-1); - local($gs, $guess) = (1, sprintf("1E%+d", (length($xm)+$xe-1)/2)); - while ($gs < 2*$scale) { - $guess = fmul(fadd($guess,fdiv($x,$guess,$gs*2)),".5"); - $gs *= 2; - } - new Math::BigFloat &fround($guess, $scale); +sub DESTROY + { + # going trough AUTOLOAD for every DESTROY is costly, so avoid it by empty sub + } + +sub AUTOLOAD + { + # make fxxx and bxxx work + # my $self = $_[0]; + my $name = $AUTOLOAD; + + $name =~ s/.*:://; # split package + #print "$name\n"; + if (!method_valid($name)) + { + #no strict 'refs'; + ## try one level up + #&{$class."::SUPER->$name"}(@_); + # delayed load of Carp and avoid recursion + require Carp; + Carp::croak ("Can't call $class\-\>$name, not a valid method"); } -} + no strict 'refs'; + my $bname = $name; $bname =~ s/^f/b/; + *{$class."\:\:$name"} = \&$bname; + &$bname; # uses @_ + } + +sub exponent + { + # return a copy of the exponent + my $self = shift; + $self = $class->new($self) unless ref $self; + + return bnan() if $self->is_nan(); + return $self->{_e}->copy(); + } + +sub mantissa + { + # return a copy of the mantissa + my $self = shift; + $self = $class->new($self) unless ref $self; + + return bnan() if $self->is_nan(); + my $m = $self->{_m}->copy(); # faster than going via bstr() + $m->bneg() if $self->{sign} eq '-'; + + return $m; + } + +sub parts + { + # return a copy of both the exponent and the mantissa + my $self = shift; + $self = $class->new($self) unless ref $self; + + return (bnan(),bnan()) if $self->is_nan(); + my $m = $self->{_m}->copy(); # faster than going via bstr() + $m->bneg() if $self->{sign} eq '-'; + return ($m,$self->{_e}->copy()); + } + +############################################################################## +# private stuff (internal use only) + +sub _one + { + # internal speedup, set argument to 1, or create a +/- 1 + # uses internal knowledge about MBI, thus (bad) + my $self = shift; + my $x = $self->bzero(); + $x->{_m}->{value} = [ 1 ]; $x->{_m}->{sign} = '+'; + $x->{_e}->{value} = [ 0 ]; $x->{_e}->{sign} = '+'; + $x->{sign} = shift || '+'; + return $x; + } + +sub import + { + my $self = shift; + #print "import $self\n"; + for ( my $i = 0; $i < @_ ; $i++ ) + { + if ( $_[$i] eq ':constant' ) + { + # this rest causes overlord er load to step in + # print "overload @_\n"; + overload::constant float => sub { $self->new(shift); }; + splice @_, $i, 1; last; + } + } + # any non :constant stuff is handled by our parent, Exporter + # even if @_ is empty, to give it a chance + #$self->SUPER::import(@_); # does not work (would call MBI) + $self->export_to_level(1,$self,@_); # need this instead + } + +sub bnorm + { + # adjust m and e so that m is smallest possible + # round number according to accuracy and precision settings + my $x = shift; + + return $x if $x->is_nan(); + + my $zeros = $x->{_m}->_trailing_zeros(); # correct for trailing zeros + if ($zeros != 0) + { + $x->{_m}->brsft($zeros,10); $x->{_e} += $zeros; + } + # for something like 0Ey, set y to 1 + $x->{_e}->bzero()->binc() if $x->{_m}->is_zero(); + return $x->SUPER::bnorm(@_); # call MBI bnorm for round + } + +############################################################################## +# internal calculation routines + +sub as_number + { + # return a bigint representation of this BigFloat number + my ($self,$x) = objectify(1,@_); + + my $z; + if ($x->{_e}->is_zero()) + { + $z = $x->{_m}->copy(); + $z->{sign} = $x->{sign}; + return $z; + } + if ($x->{_e} < 0) + { + $x->{_e}->babs(); + my $y = $x->{_m} / ($ten ** $x->{_e}); + $x->{_e}->bneg(); + $y->{sign} = $x->{sign}; + return $y; + } + $z = $x->{_m} * ($ten ** $x->{_e}); + $z->{sign} = $x->{sign}; + return $z; + } + +sub length + { + my $x = shift; $x = $class->new($x) unless ref $x; + + my $len = $x->{_m}->length(); + $len += $x->{_e} if $x->{_e}->sign() eq '+'; + if (wantarray()) + { + my $t = Math::BigInt::bzero(); + $t = $x->{_e}->copy()->babs() if $x->{_e}->sign() eq '-'; + return ($len,$t); + } + return $len; + } 1; __END__ =head1 NAME -Math::BigFloat - Arbitrary length float math package +Math::BigFloat - Arbitrary size floating point math package =head1 SYNOPSIS use Math::BigFloat; - $f = Math::BigFloat->new($string); - - $f->fadd(NSTR) return NSTR addition - $f->fsub(NSTR) return NSTR subtraction - $f->fmul(NSTR) return NSTR multiplication - $f->fdiv(NSTR[,SCALE]) returns NSTR division to SCALE places - $f->fmod(NSTR) returns NSTR modular remainder - $f->fneg() return NSTR negation - $f->fabs() return NSTR absolute value - $f->fcmp(NSTR) return CODE compare undef,<0,=0,>0 - $f->fround(SCALE) return NSTR round to SCALE digits - $f->ffround(SCALE) return NSTR round at SCALEth place - $f->fnorm() return (NSTR) normalize - $f->fsqrt([SCALE]) return NSTR sqrt to SCALE places + + # Number creation + $x = Math::BigInt->new($str); # defaults to 0 + $nan = Math::BigInt->bnan(); # create a NotANumber + $zero = Math::BigInt->bzero();# create a "+0" + + # Testing + $x->is_zero(); # return whether arg is zero or not + $x->is_one(); # return true if arg is +1 + $x->is_one('-'); # return true if arg is -1 + $x->is_odd(); # return true if odd, false for even + $x->is_even(); # return true if even, false for odd + $x->bcmp($y); # compare numbers (undef,<0,=0,>0) + $x->bacmp($y); # compare absolutely (undef,<0,=0,>0) + $x->sign(); # return the sign, either +,- or NaN + + # The following all modify their first argument: + + # set + $x->bzero(); # set $i to 0 + $x->bnan(); # set $i to NaN + + $x->bneg(); # negation + $x->babs(); # absolute value + $x->bnorm(); # normalize (no-op) + $x->bnot(); # two's complement (bit wise not) + $x->binc(); # increment x by 1 + $x->bdec(); # decrement x by 1 + + $x->badd($y); # addition (add $y to $x) + $x->bsub($y); # subtraction (subtract $y from $x) + $x->bmul($y); # multiplication (multiply $x by $y) + $x->bdiv($y); # divide, set $i to quotient + # return (quo,rem) or quo if scalar + + $x->bmod($y); # modulus + $x->bpow($y); # power of arguments (a**b) + $x->blsft($y); # left shift + $x->brsft($y); # right shift + # return (quo,rem) or quo if scalar + + $x->band($y); # bit-wise and + $x->bior($y); # bit-wise inclusive or + $x->bxor($y); # bit-wise exclusive or + $x->bnot(); # bit-wise not (two's complement) + + $x->bround($N); # accuracy: preserver $N digits + $x->bfround($N); # precision: round to the $Nth digit + + # The following do not modify their arguments: + + bgcd(@values); # greatest common divisor + blcm(@values); # lowest common multiplicator + + $x->bstr(); # return string + $x->bsstr(); # return string in scientific notation + + $x->exponent(); # return exponent as BigInt + $x->mantissa(); # return mantissa as BigInt + $x->parts(); # return (mantissa,exponent) as BigInt + + $x->length(); # number of digits (w/o sign and '.') + ($l,$f) = $x->length(); # number of digits, and length of fraction =head1 DESCRIPTION -All basic math operations are overloaded if you declare your big -floats as +All operators (inlcuding basic math operations) are overloaded if you +declare your big floating point numbers as - $float = new Math::BigFloat "2.123123123123123123123123123123123"; + $i = new Math::BigFloat '12_3.456_789_123_456_789E-2'; + +Operations with overloaded operators preserve the arguments, which is +exactly what you expect. + +=head2 Canonical notation + +Input to these routines are either BigFloat objects, or strings of the +following four forms: =over 2 -=item number format +=item * + +C</^[+-]\d+$/> -canonical strings have the form /[+-]\d+E[+-]\d+/ . Input values can -have embedded whitespace. +=item * -=item Error returns 'NaN' +C</^[+-]\d+\.\d*$/> -An input parameter was "Not a Number" or divide by zero or sqrt of -negative number. +=item * -=item Division is computed to +C</^[+-]\d+E[+-]?\d+$/> -C<max($Math::BigFloat::div_scale,length(dividend)+length(divisor))> -digits by default. -Also used for default sqrt scale. +=item * -=item Rounding is performed +C</^[+-]\d*\.\d+E[+-]?\d+$/> -according to the value of -C<$Math::BigFloat::rnd_mode>: +=back + +all with optional leading and trailing zeros and/or spaces. Additonally, +numbers are allowed to have an underscore between any two digits. + +Empty strings as well as other illegal numbers results in 'NaN'. + +bnorm() on a BigFloat object is now effectively a no-op, since the numbers +are always stored in normalized form. On a string, it creates a BigFloat +object. + +=head2 Output + +Output values are BigFloat objects (normalized), except for bstr() and bsstr(). + +The string output will always have leading and trailing zeros stripped and drop +a plus sign. C<bstr()> will give you always the form with a decimal point, +while C<bsstr()> (for scientific) gives you the scientific notation. + + Input bstr() bsstr() + '-0' '0' '0E1' + ' -123 123 123' '-123123123' '-123123123E0' + '00.0123' '0.0123' '123E-4' + '123.45E-2' '1.2345' '12345E-4' + '10E+3' '10000' '1E4' + +Some routines (C<is_odd()>, C<is_even()>, C<is_zero()>, C<is_one()>, +C<is_nan()>) return true or false, while others (C<bcmp()>, C<bacmp()>) +return either undef, <0, 0 or >0 and are suited for sort. + +Actual math is done by using BigInts to represent the mantissa and exponent. +The sign C</^[+-]$/> is stored separately. The string 'NaN' is used to +represent the result when input arguments are not numbers, as well as +the result of dividing by zero. + +=head2 C<mantissa()>, C<exponent()> and C<parts()> + +C<mantissa()> and C<exponent()> return the said parts of the BigFloat +as BigInts such that: + + $m = $x->mantissa(); + $e = $x->exponent(); + $y = $m * ( 10 ** $e ); + print "ok\n" if $x == $y; + +C<< ($m,$e) = $x->parts(); >> is just a shortcut giving you both of them. + +A zero is represented and returned as C<0E1>, B<not> C<0E0> (after Knuth). + +Currently the mantissa is reduced as much as possible, favouring higher +exponents over lower ones (e.g. returning 1e7 instead of 10e6 or 10000000e0). +This might change in the future, so do not depend on it. + +=head2 Accuracy vs. Precision + +See also: L<Rounding|Rounding>. + +Math::BigFloat supports both precision and accuracy. (here should follow +a short description of both). - trunc truncate the value - zero round towards 0 - +inf round towards +infinity (round up) - -inf round towards -infinity (round down) - even round to the nearest, .5 to the even digit - odd round to the nearest, .5 to the odd digit +Precision: digits after the '.', laber, schwad +Accuracy: Significant digits blah blah -The default is C<even> rounding. +Since things like sqrt(2) or 1/3 must presented with a limited precision lest +a operation consumes all resources, each operation produces no more than +C<Math::BigFloat::precision()> digits. + +In case the result of one operation has more precision than specified, +it is rounded. The rounding mode taken is either the default mode, or the one +supplied to the operation after the I<scale>: + + $x = Math::BigFloat->new(2); + Math::BigFloat::precision(5); # 5 digits max + $y = $x->copy()->bdiv(3); # will give 0.66666 + $y = $x->copy()->bdiv(3,6); # will give 0.666666 + $y = $x->copy()->bdiv(3,6,'odd'); # will give 0.666667 + Math::BigFloat::round_mode('zero'); + $y = $x->copy()->bdiv(3,6); # will give 0.666666 + +=head2 Rounding + +=over 2 + +=item ffround ( +$scale ) rounds to the $scale'th place left from the '.', counting from the dot. The first digit is numbered 1. + +=item ffround ( -$scale ) rounds to the $scale'th place right from the '.', counting from the dot + +=item ffround ( 0 ) rounds to an integer + +=item fround ( +$scale ) preserves accuracy to $scale digits from the left (aka significant digits) and paddes the rest with zeros. If the number is between 1 and -1, the significant digits count from the first non-zero after the '.' + +=item fround ( -$scale ) and fround ( 0 ) are a no-ops =back +All rounding functions take as a second parameter a rounding mode from one of +the following: 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'. + +The default rounding mode is 'even'. By using +C<< Math::BigFloat::round_mode($rnd_mode); >> you can get and set the default +mode for subsequent rounding. The usage of C<$Math::BigFloat::$rnd_mode> is +no longer supported. + The second parameter to the round functions then overrides the default +temporarily. + +The C<< as_number() >> function returns a BigInt from a Math::BigFloat. It uses +'trunc' as rounding mode to make it equivalent to: + + $x = 2.5; + $y = int($x) + 2; + +You can override this by passing the desired rounding mode as parameter to +C<as_number()>: + + $x = Math::BigFloat->new(2.5); + $y = $x->as_number('odd'); # $y = 3 + +=head1 EXAMPLES + + use Math::BigFloat qw(bstr bint); + # not ready yet + $x = bstr("1234") # string "1234" + $x = "$x"; # same as bstr() + $x = bneg("1234") # BigFloat "-1234" + $x = Math::BigFloat->bneg("1234"); # BigFloat "1234" + $x = Math::BigFloat->babs("-12345"); # BigFloat "12345" + $x = Math::BigFloat->bnorm("-0 00"); # BigFloat "0" + $x = bint(1) + bint(2); # BigFloat "3" + $x = bint(1) + "2"; # ditto (auto-BigFloatify of "2") + $x = bint(1); # BigFloat "1" + $x = $x + 5 / 2; # BigFloat "3" + $x = $x ** 3; # BigFloat "27" + $x *= 2; # BigFloat "54" + $x = new Math::BigFloat; # BigFloat "0" + $x--; # BigFloat "-1" + +=head1 Autocreating constants + +After C<use Math::BigFloat ':constant'> all the floating point constants +in the given scope are converted to C<Math::BigFloat>. This conversion +happens at compile time. + +In particular + + perl -MMath::BigFloat=:constant -e 'print 2E-100,"\n"' + +prints the value of C<2E-100>. Note that without conversion of +constants the expression 2E-100 will be calculated as normal floating point +number. + +=head1 PERFORMANCE + +Greatly enhanced ;o) +SectionNotReadyYet. + =head1 BUGS -The current version of this module is a preliminary version of the -real thing that is currently (as of perl5.002) under development. +=over 2 + +=item * + +The following does not work yet: + + $m = $x->mantissa(); + $e = $x->exponent(); + $y = $m * ( 10 ** $e ); + print "ok\n" if $x == $y; + +=item * + +There is no fmod() function yet. + +=back + +=head1 CAVEAT + +=over 1 + +=item stringify, bstr() + +Both stringify and bstr() now drop the leading '+'. The old code would return +'+1.23', the new returns '1.23'. See the documentation in L<Math::BigInt> for +reasoning and details. + +=item bdiv + +The following will probably not do what you expect: + + print $c->bdiv(123.456),"\n"; + +It prints both quotient and reminder since print works in list context. Also, +bdiv() will modify $c, so be carefull. You probably want to use + + print $c / 123.456,"\n"; + print scalar $c->bdiv(123.456),"\n"; # or if you want to modify $c + +instead. + +=item Modifying and = + +Beware of: + + $x = Math::BigFloat->new(5); + $y = $x; + +It will not do what you think, e.g. making a copy of $x. Instead it just makes +a second reference to the B<same> object and stores it in $y. Thus anything +that modifies $x will modify $y, and vice versa. + + $x->bmul(2); + print "$x, $y\n"; # prints '10, 10' + +If you want a true copy of $x, use: + + $y = $x->copy(); + +See also the documentation in L<overload> regarding C<=>. + +=item bpow + +C<bpow()> now modifies the first argument, unlike the old code which left +it alone and only returned the result. This is to be consistent with +C<badd()> etc. The first will modify $x, the second one won't: + + print bpow($x,$i),"\n"; # modify $x + print $x->bpow($i),"\n"; # ditto + print $x ** $i,"\n"; # leave $x alone + +=back + +=head1 LICENSE -The printf subroutine does not use the value of -C<$Math::BigFloat::rnd_mode> when rounding values for printing. -Consequently, the way to print rounded values is -to specify the number of digits both as an -argument to C<ffround> and in the C<%f> printf string, -as follows: +This program is free software; you may redistribute it and/or modify it under +the same terms as Perl itself. - printf "%.3f\n", $bigfloat->ffround(-3); +=head1 AUTHORS -=head1 AUTHOR +Mark Biggar, overloaded interface by Ilya Zakharevich. +Completely rewritten by Tels http://bloodgate.com in 2001. -Mark Biggar -Patches by John Peacock Apr 2001 =cut diff --git a/lib/Math/BigInt.pm b/lib/Math/BigInt.pm index 745e1c6479..53d5b11944 100644 --- a/lib/Math/BigInt.pm +++ b/lib/Math/BigInt.pm @@ -1,440 +1,2134 @@ -package Math::BigInt; -require Exporter; -@ISA = qw(Exporter); +#!/usr/bin/perl -w + +# mark.biggar@TrustedSysLabs.com +# eay@mincom.com is dead (math::BigInteger) +# see: http://www.cypherspace.org/~adam/rsa/pureperl.html (contacted c. adam +# on 2000/11/13 - but email is dead + +# todo: +# - fully remove funky $# stuff (maybe) +# - use integer; vs 1e7 as base +# - speed issues (XS? Bit::Vector?) +# - split out actual math code to Math::BigNumber + +# Qs: what exactly happens on numify of HUGE numbers? overflow? +# $a = -$a is much slower (making copy of $a) than $a->bneg(), hm!? +# (copy_on_write will help there, but that is not yet implemented) + +# The following hash values are used: +# value: the internal array, base 100000 +# sign : +,-,NaN,+inf,-inf +# _a : accuracy +# _p : precision +# _cow : copy on write: number of objects that share the data (NRY) +# Internally the numbers are stored in an array with at least 1 element, no +# leading zero parts (except the first) and in base 100000 + +# USE_MUL: due to problems on certain os (os390, posix-bc) "* 1e-5" is used +# instead of "/ 1e5" at some places, (marked with USE_MUL). But instead of +# using the reverse only on problematic machines, I used it everytime to avoid +# the costly comparisations. This _should_ work everywhere. Thanx Peter Prymmer -$VERSION='0.02'; +package Math::BigInt; +my $class = "Math::BigInt"; + +$VERSION = 1.35; +use Exporter; +@ISA = qw( Exporter ); +@EXPORT_OK = qw( bneg babs bcmp badd bmul bdiv bmod bnorm bsub + bgcd blcm + bround + blsft brsft band bior bxor bnot bpow bnan bzero + bacmp bstr bsstr binc bdec bint binf bfloor bceil + is_odd is_even is_zero is_one is_nan is_inf sign + length as_number + trace objectify _swap + ); + +#@EXPORT = qw( ); +use vars qw/$rnd_mode $accuracy $precision $div_scale/; +use strict; + +# Inside overload, the first arg is always an object. If the original code had +# it reversed (like $x = 2 * $y), then the third paramater indicates this +# swapping. To make it work, we use a helper routine which not only reswaps the +# params, but also makes a new object in this case. See _swap() for details, +# especially the cases of operators with different classes. + +# For overloaded ops with only one argument we simple use $_[0]->copy() to +# preserve the argument. + +# Thus inheritance of overload operators becomes possible and transparent for +# our subclasses without the need to repeat the entire overload section there. use overload -'+' => sub {new Math::BigInt &badd}, -'-' => sub {new Math::BigInt - $_[2]? bsub($_[1],${$_[0]}) : bsub(${$_[0]},$_[1])}, -'<=>' => sub {$_[2]? bcmp($_[1],${$_[0]}) : bcmp(${$_[0]},$_[1])}, -'cmp' => sub {$_[2]? ($_[1] cmp ${$_[0]}) : (${$_[0]} cmp $_[1])}, -'*' => sub {new Math::BigInt &bmul}, -'/' => sub {new Math::BigInt - $_[2]? scalar bdiv($_[1],${$_[0]}) : - scalar bdiv(${$_[0]},$_[1])}, -'%' => sub {new Math::BigInt - $_[2]? bmod($_[1],${$_[0]}) : bmod(${$_[0]},$_[1])}, -'**' => sub {new Math::BigInt - $_[2]? bpow($_[1],${$_[0]}) : bpow(${$_[0]},$_[1])}, -'neg' => sub {new Math::BigInt &bneg}, -'abs' => sub {new Math::BigInt &babs}, -'<<' => sub {new Math::BigInt - $_[2]? blsft($_[1],${$_[0]}) : blsft(${$_[0]},$_[1])}, -'>>' => sub {new Math::BigInt - $_[2]? brsft($_[1],${$_[0]}) : brsft(${$_[0]},$_[1])}, -'&' => sub {new Math::BigInt &band}, -'|' => sub {new Math::BigInt &bior}, -'^' => sub {new Math::BigInt &bxor}, -'~' => sub {new Math::BigInt &bnot}, -'int' => sub { shift }, +'=' => sub { $_[0]->copy(); }, + +# '+' and '-' do not use _swap, since it is a triffle slower. If you want to +# override _swap (if ever), then override overload of '+' and '-', too! +# for sub it is a bit tricky to keep b: b-a => -a+b +'-' => sub { my $c = $_[0]->copy; $_[2] ? + $c->bneg()->badd($_[1]) : + $c->bsub( $_[1]) }, +'+' => sub { $_[0]->copy()->badd($_[1]); }, + +# some shortcuts for speed (assumes that reversed order of arguments is routed +# to normal '+' and we thus can always modify first arg. If this is changed, +# this breaks and must be adjusted.) +'+=' => sub { $_[0]->badd($_[1]); }, +'-=' => sub { $_[0]->bsub($_[1]); }, +'*=' => sub { $_[0]->bmul($_[1]); }, +'/=' => sub { scalar $_[0]->bdiv($_[1]); }, +'**=' => sub { $_[0]->bpow($_[1]); }, + +'<=>' => sub { $_[2] ? + $class->bcmp($_[1],$_[0]) : + $class->bcmp($_[0],$_[1])}, +'cmp' => sub { + $_[2] ? + $_[1] cmp $_[0]->bstr() : + $_[0]->bstr() cmp $_[1] }, + +'int' => sub { $_[0]->copy(); }, +'neg' => sub { $_[0]->copy()->bneg(); }, +'abs' => sub { $_[0]->copy()->babs(); }, +'~' => sub { $_[0]->copy()->bnot(); }, + +'*' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->bmul($a[1]); }, +'/' => sub { my @a = ref($_[0])->_swap(@_);scalar $a[0]->bdiv($a[1]);}, +'%' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->bmod($a[1]); }, +'**' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->bpow($a[1]); }, +'<<' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->blsft($a[1]); }, +'>>' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->brsft($a[1]); }, + +'&' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->band($a[1]); }, +'|' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->bior($a[1]); }, +'^' => sub { my @a = ref($_[0])->_swap(@_); $a[0]->bxor($a[1]); }, + +# can modify arg of ++ and --, so avoid a new-copy for speed, but don't +# use $_[0]->_one(), it modifies $_[0] to be 1! +'++' => sub { $_[0]->binc() }, +'--' => sub { $_[0]->bdec() }, + +# if overloaded, O(1) instead of O(N) and twice as fast for small numbers +'bool' => sub { + # this kludge is needed for perl prior 5.6.0 since returning 0 here fails :-/ + # v5.6.1 dumps on that: return !$_[0]->is_zero() || undef; :-( + my $t = !$_[0]->is_zero(); + undef $t if $t == 0; + return $t; + }, qw( -"" stringify -0+ numify) # Order of arguments unsignificant +"" bstr +0+ numify), # Order of arguments unsignificant ; -$NaNOK=1; - -sub new { - my($class) = shift; - my($foo) = bnorm(shift); - die "Not a number initialized to Math::BigInt" if !$NaNOK && $foo eq "NaN"; - bless \$foo, $class; -} -sub stringify { "${$_[0]}" } -sub numify { 0 + "${$_[0]}" } # Not needed, additional overhead - # comparing to direct compilation based on - # stringify -sub import { +############################################################################## +# global constants, flags and accessory + +# are NaNs ok? +my $NaNOK=1; +# set to 1 for tracing +my $trace = 0; +# constants for easier life +my $nan = 'NaN'; +my $BASE_LEN = 5; +my $BASE = int("1e".$BASE_LEN); # var for trying to change it to 1e7 +my $RBASE = 1e-5; # see USE_MUL + +# Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc' +$rnd_mode = 'even'; +$accuracy = undef; +$precision = undef; +$div_scale = 40; + +sub round_mode + { + # make Class->round_mode() work + my $self = shift || $class; + # shift @_ if defined $_[0] && $_[0] eq $class; + if (defined $_[0]) + { + my $m = shift; + die "Unknown round mode $m" + if $m !~ /^(even|odd|\+inf|\-inf|zero|trunc)$/; + $rnd_mode = $m; return; + } + return $rnd_mode; + } + +sub accuracy + { + # $x->accuracy($a); ref($x) a + # $x->accuracy(); ref($x); + # Class::accuracy(); # not supported + #print "MBI @_ ($class)\n"; + my $x = shift; + + die ("accuracy() needs reference to object as first parameter.") + if !ref $x; + + if (@_ > 0) + { + $x->{_a} = shift; + $x->round() if defined $x->{_a}; + } + return $x->{_a}; + } + +sub precision + { + my $x = shift; + + die ("precision() needs reference to object as first parameter.") + unless ref $x; + + if (@_ > 0) + { + $x->{_p} = shift; + $x->round() if defined $x->{_p}; + } + return $x->{_p}; + } + +sub _scale_a + { + # select accuracy parameter based on precedence, + # used by bround() and bfround(), may return undef for scale (means no op) + my ($x,$s,$m,$scale,$mode) = @_; + $scale = $x->{_a} if !defined $scale; + $scale = $s if (!defined $scale); + $mode = $m if !defined $mode; + return ($scale,$mode); + } + +sub _scale_p + { + # select precision parameter based on precedence, + # used by bround() and bfround(), may return undef for scale (means no op) + my ($x,$s,$m,$scale,$mode) = @_; + $scale = $x->{_p} if !defined $scale; + $scale = $s if (!defined $scale); + $mode = $m if !defined $mode; + return ($scale,$mode); + } + +############################################################################## +# constructors + +sub copy + { + my ($c,$x); + if (@_ > 1) + { + # if two arguments, the first one is the class to "swallow" subclasses + ($c,$x) = @_; + } + else + { + $x = shift; + $c = ref($x); + } + return unless ref($x); # only for objects + + my $self = {}; bless $self,$c; + foreach my $k (keys %$x) + { + if (ref($x->{$k}) eq 'ARRAY') + { + $self->{$k} = [ @{$x->{$k}} ]; + } + elsif (ref($x->{$k}) eq 'HASH') + { + # only one level deep! + foreach my $h (keys %{$x->{$k}}) + { + $self->{$k}->{$h} = $x->{$k}->{$h}; + } + } + elsif (ref($x->{$k})) + { + my $c = ref($x->{$k}); + $self->{$k} = $c->new($x->{$k}); # no copy() due to deep rec + } + else + { + $self->{$k} = $x->{$k}; + } + } + $self; + } + +sub new + { + # create a new BigInts object from a string or another bigint object. + # value => internal array representation + # sign => sign (+/-), or "NaN" + + # the argument could be an object, so avoid ||, && etc on it, this would + # cause costly overloaded code to be called. The only allowed op are ref() + # and definend. + + trace (@_); + my $class = shift; + + my $wanted = shift; # avoid numify call by not using || here + return $class->bzero() if !defined $wanted; # default to 0 + return $class->copy($wanted) if ref($wanted); + + my $self = {}; bless $self, $class; + # handle '+inf', '-inf' first + if ($wanted =~ /^[+-]inf$/) + { + $self->{value} = [ 0 ]; + $self->{sign} = $wanted; + return $self; + } + # split str in m mantissa, e exponent, i integer, f fraction, v value, s sign + my ($mis,$miv,$mfv,$es,$ev) = _split(\$wanted); + if (ref $mis && !ref $miv) + { + # _from_hex + $self->{value} = $mis->{value}; + $self->{sign} = $mis->{sign}; + return $self; + } + if (!ref $mis) + { + die "$wanted is not a number initialized to $class" if !$NaNOK; + #print "NaN 1\n"; + $self->{value} = [ 0 ]; + $self->{sign} = $nan; + return $self; + } + # make integer from mantissa by adjusting exp, then convert to bigint + $self->{sign} = $$mis; # store sign + $self->{value} = [ 0 ]; # for all the NaN cases + my $e = int("$$es$$ev"); # exponent (avoid recursion) + if ($e > 0) + { + my $diff = $e - CORE::length($$mfv); + if ($diff < 0) # Not integer + { + #print "NOI 1\n"; + $self->{sign} = $nan; + } + else # diff >= 0 + { + # adjust fraction and add it to value + # print "diff > 0 $$miv\n"; + $$miv = $$miv . ($$mfv . '0' x $diff); + } + } + else + { + if ($$mfv ne '') # e <= 0 + { + # fraction and negative/zero E => NOI + #print "NOI 2 \$\$mfv '$$mfv'\n"; + $self->{sign} = $nan; + } + elsif ($e < 0) + { + # xE-y, and empty mfv + #print "xE-y\n"; + $e = abs($e); + if ($$miv !~ s/0{$e}$//) # can strip so many zero's? + { + #print "NOI 3\n"; + $self->{sign} = $nan; + } + } + } + $self->{sign} = '+' if $$miv eq '0'; # normalize -0 => +0 + $self->_internal($miv) if $self->{sign} ne $nan; # as internal array + #print "$wanted => $self->{sign} $self->{value}->[0]\n"; + # if any of the globals is set, round to them and thus store them insid $self + $self->round($accuracy,$precision,$rnd_mode) + if defined $accuracy || defined $precision; + return $self; + } + +# some shortcuts for easier life +sub bint + { + # exportable version of new + trace(@_); + return $class->new(@_); + } + +sub bnan + { + # create a bigint 'NaN', if given a BigInt, set it to 'NaN' my $self = shift; - return unless @_; - for ( my $i; $i < @_ ; $i++ ) { - if ( $_[$i] eq ':constant' ) { - overload::constant integer => sub {Math::BigInt->new(shift)}; - splice @_, $i, 1; - last; - } + $self = $class if !defined $self; + if (!ref($self)) + { + my $c = $self; $self = {}; bless $self, $c; + } + return if $self->modify('bnan'); + $self->{value} = [ 0 ]; + $self->{sign} = $nan; + trace('NaN'); + return $self; } - $self->SUPER::import(@_); -} - -$zero = 0; - -# overcome a floating point problem on certain osnames (posix-bc, os390) -BEGIN { - my $x = 100000.0; - my $use_mult = int($x*1e-5)*1e5 == $x ? 1 : 0; -} - -# normalize string form of number. Strip leading zeros. Strip any -# white space and add a sign, if missing. -# Strings that are not numbers result the value 'NaN'. - -sub bnorm { #(num_str) return num_str - local($_) = @_; - s/\s+//g; # strip white space - if (s/^([+-]?)0*(\d+)$/$1$2/) { # test if number - substr($_,$[,0) = '+' unless $1; # Add missing sign - s/^-0/+0/; - $_; - } else { - 'NaN'; - } -} - -# Convert a number from string format to internal base 100000 format. -# Assumes normalized value as input. -sub internal { #(num_str) return int_num_array - local($d) = @_; - ($is,$il) = (substr($d,$[,1),length($d)-2); - substr($d,$[,1) = ''; - ($is, reverse(unpack("a" . ($il%5+1) . ("a5" x ($il/5)), $d))); -} - -# Convert a number from internal base 100000 format to string format. -# This routine scribbles all over input array. -sub external { #(int_num_array) return num_str - $es = shift; - grep($_ > 9999 || ($_ = substr('0000'.$_,-5)), @_); # zero pad - &bnorm(join('', $es, reverse(@_))); # reverse concat and normalize -} - -# Negate input value. -sub bneg { #(num_str) return num_str - local($_) = &bnorm(@_); - return $_ if $_ eq '+0' or $_ eq 'NaN'; - vec($_,0,8) ^= ord('+') ^ ord('-'); - $_; -} - -# Returns the absolute value of the input. -sub babs { #(num_str) return num_str - &abs(&bnorm(@_)); -} - -sub abs { # post-normalized abs for internal use - local($_) = @_; - s/^-/+/; - $_; -} - -# Compares 2 values. Returns one of undef, <0, =0, >0. (suitable for sort) -sub bcmp { #(num_str, num_str) return cond_code - local($x,$y) = (&bnorm($_[$[]),&bnorm($_[$[+1])); - if ($x eq 'NaN') { - undef; - } elsif ($y eq 'NaN') { - undef; - } else { - &cmp($x,$y) <=> 0; - } -} - -sub cmp { # post-normalized compare for internal use - local($cx, $cy) = @_; - - return 0 if ($cx eq $cy); - - local($sx, $sy) = (substr($cx, 0, 1), substr($cy, 0, 1)); - local($ld); - - if ($sx eq '+') { - return 1 if ($sy eq '-' || $cy eq '+0'); - $ld = length($cx) - length($cy); - return $ld if ($ld); - return $cx cmp $cy; - } else { # $sx eq '-' - return -1 if ($sy eq '+'); - $ld = length($cy) - length($cx); - return $ld if ($ld); - return $cy cmp $cx; - } -} - -sub badd { #(num_str, num_str) return num_str - local(*x, *y); ($x, $y) = (&bnorm($_[$[]),&bnorm($_[$[+1])); - if ($x eq 'NaN') { - 'NaN'; - } elsif ($y eq 'NaN') { - 'NaN'; - } else { - @x = &internal($x); # convert to internal form - @y = &internal($y); - local($sx, $sy) = (shift @x, shift @y); # get signs - if ($sx eq $sy) { - &external($sx, &add(*x, *y)); # if same sign add - } else { - ($x, $y) = (&abs($x),&abs($y)); # make abs - if (&cmp($y,$x) > 0) { - &external($sy, &sub(*y, *x)); - } else { - &external($sx, &sub(*x, *y)); - } - } + +sub binf + { + # create a bigint '+-inf', if given a BigInt, set it to '+-inf' + # the sign is either '+', or if given, used from there + my $self = shift; + my $sign = shift; $sign = '+' if !defined $sign || $sign ne '-'; + $self = $class if !defined $self; + if (!ref($self)) + { + my $c = $self; $self = {}; bless $self, $c; + } + return if $self->modify('binf'); + $self->{value} = [ 0 ]; + $self->{sign} = $sign.'inf'; + trace('inf'); + return $self; + } + +sub bzero + { + # create a bigint '+0', if given a BigInt, set it to 0 + my $self = shift; + $self = $class if !defined $self; + if (!ref($self)) + { + my $c = $self; $self = {}; bless $self, $c; + } + return if $self->modify('bzero'); + $self->{value} = [ 0 ]; + $self->{sign} = '+'; + trace('0'); + return $self; + } + +############################################################################## +# string conversation + +sub bsstr + { + # (ref to BFLOAT or num_str ) return num_str + # Convert number from internal format to scientific string format. + # internal format is always normalized (no leading zeros, "-0E0" => "+0E0") + trace(@_); + my ($self,$x) = objectify(1,@_); + + return $x->{sign} if $x->{sign} !~ /^[+-]$/; + my ($m,$e) = $x->parts(); + # can be only '+', so + my $sign = 'e+'; + # MBF: my $s = $e->{sign}; $s = '' if $s eq '-'; my $sep = 'e'.$s; + return $m->bstr().$sign.$e->bstr(); + } + +sub bstr + { + # (ref to BINT or num_str ) return num_str + # Convert number from internal base 100000 format to string format. + # internal format is always normalized (no leading zeros, "-0" => "+0") + trace(@_); + my $x = shift; $x = $class->new($x) unless ref $x; + # my ($self,$x) = objectify(1,@_); + + return $x->{sign} if $x->{sign} !~ /^[+-]$/; + my $ar = $x->{value} || return $nan; # should not happen + my $es = ""; + $es = $x->{sign} if $x->{sign} eq '-'; # get sign, but not '+' + my $l = scalar @$ar; # number of parts + return $nan if $l < 1; # should not happen + # handle first one different to strip leading zeros from it (there are no + # leading zero parts in internal representation) + $l --; $es .= $ar->[$l]; $l--; + # Interestingly, the pre-padd method uses more time + # the old grep variant takes longer (14 to 10 sec) + while ($l >= 0) + { + $es .= substr('0000'.$ar->[$l],-5); # fastest way I could think of + $l--; } -} - -sub bsub { #(num_str, num_str) return num_str - &badd($_[$[],&bneg($_[$[+1])); -} - -# GCD -- Euclids algorithm Knuth Vol 2 pg 296 -sub bgcd { #(num_str, num_str) return num_str - local($x,$y) = (&bnorm($_[$[]),&bnorm($_[$[+1])); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - ($x, $y) = ($y,&bmod($x,$y)) while $y ne '+0'; - $x; - } -} - -# routine to add two base 1e5 numbers -# stolen from Knuth Vol 2 Algorithm A pg 231 -# there are separate routines to add and sub as per Kunth pg 233 -sub add { #(int_num_array, int_num_array) return int_num_array - local(*x, *y) = @_; - $car = 0; - for $x (@x) { - last unless @y || $car; - $x -= 1e5 if $car = (($x += (@y ? shift(@y) : 0) + $car) >= 1e5) ? 1 : 0; - } - for $y (@y) { - last unless $car; - $y -= 1e5 if $car = (($y += $car) >= 1e5) ? 1 : 0; - } - (@x, @y, $car); -} - -# subtract base 1e5 numbers -- stolen from Knuth Vol 2 pg 232, $x > $y -sub sub { #(int_num_array, int_num_array) return int_num_array - local(*sx, *sy) = @_; - $bar = 0; - for $sx (@sx) { - last unless @sy || $bar; - $sx += 1e5 if $bar = (($sx -= (@sy ? shift(@sy) : 0) + $bar) < 0); - } - @sx; -} - -# multiply two numbers -- stolen from Knuth Vol 2 pg 233 -sub bmul { #(num_str, num_str) return num_str - local(*x, *y); ($x, $y) = (&bnorm($_[$[]), &bnorm($_[$[+1])); - if ($x eq 'NaN') { - 'NaN'; - } elsif ($y eq 'NaN') { - 'NaN'; - } else { - @x = &internal($x); - @y = &internal($y); - &external(&mul(*x,*y)); - } -} - -# multiply two numbers in internal representation -# destroys the arguments, supposes that two arguments are different -sub mul { #(*int_num_array, *int_num_array) return int_num_array - local(*x, *y) = (shift, shift); - local($signr) = (shift @x ne shift @y) ? '-' : '+'; - @prod = (); - for $x (@x) { - ($car, $cty) = (0, $[); - for $y (@y) { - $prod = $x * $y + ($prod[$cty] || 0) + $car; - if ($use_mult) { - $prod[$cty++] = - $prod - ($car = int($prod * 1e-5)) * 1e5; + return $es; + } + +sub numify + { + # Make a number from a BigInt object + # old: simple return string and let Perl's atoi() handle the rest + # new: calc because it is faster than bstr()+atoi() + #trace (@_); + #my ($self,$x) = objectify(1,@_); + #return $x->bstr(); # ref($x); + my $x = shift; $x = $class->new($x) unless ref $x; + + return $nan if $x->{sign} eq $nan; + my $fac = 1; $fac = -1 if $x->{sign} eq '-'; + return $fac*$x->{value}->[0] if @{$x->{value}} == 1; # below $BASE + my $num = 0; + foreach (@{$x->{value}}) + { + $num += $fac*$_; $fac *= $BASE; + } + return $num; + } + +############################################################################## +# public stuff (usually prefixed with "b") + +sub sign + { + # return the sign of the number: +/-/NaN + my ($self,$x) = objectify(1,@_); + return $x->{sign}; + } + +sub round + { + # After any operation or when calling round(), the result is rounded by + # regarding the A & P from arguments, local parameters, or globals. + # The result's A or P are set by the rounding, but not inspected beforehand + # (aka only the arguments enter into it). This works because the given + # 'first' argument is both the result and true first argument with unchanged + # A and P settings. + # This does not yet handle $x with A, and $y with P (which should be an + # error). + my $self = shift; + my $a = shift; # accuracy, if given by caller + my $p = shift; # precision, if given by caller + my $r = shift; # round_mode, if given by caller + my @args = @_; # all 'other' arguments (0 for unary, 1 for binary ops) + + unshift @args,$self; # add 'first' argument + + $self = new($self) unless ref($self); # if not object, make one + + # find out class of argument to round + my $c = ref($args[0]); + + # now pick $a or $p, but only if we have got "arguments" + if ((!defined $a) && (!defined $p) && (@args > 0)) + { + foreach (@args) + { + # take the defined one, or if both defined, the one that is smaller + $a = $_->{_a} if (defined $_->{_a}) && (!defined $a || $_->{_a} < $a); + } + if (!defined $a) # if it still is not defined, take p + { + foreach (@args) + { + # take the defined one, or if both defined, the one that is smaller + $p = $_->{_p} if (defined $_->{_p}) && (!defined $p || $_->{_p} < $p); } - else { - $prod[$cty++] = - $prod - ($car = int($prod / 1e5)) * 1e5; + # if none defined, use globals (#2) + if (!defined $p) + { + no strict 'refs'; + my $z = "$c\::accuracy"; $a = $$z; + if (!defined $a) + { + $z = "$c\::precision"; $p = $$z; + } } + } # endif !$a + } # endif !$a || !$P && args > 0 + # for clearity, this is not merged at place (#2) + # now round, by calling fround or ffround: + if (defined $a) + { + $self->{_a} = $a; $self->bround($a,$r); + } + elsif (defined $p) + { + $self->{_p} = $p; $self->bfround($p,$r); + } + return $self->bnorm(); + } + +sub bnorm + { + # (num_str or BINT) return BINT + # Normalize number -- no-op here + my $self = shift; + + return $self; + } + +sub babs + { + # (BINT or num_str) return BINT + # make number absolute, or return absolute BINT from string + #my ($self,$x) = objectify(1,@_); + my $x = shift; $x = $class->new($x) unless ref $x; + return $x if $x->modify('babs'); + # post-normalized abs for internal use (does nothing for NaN) + $x->{sign} =~ s/^-/+/; + $x; + } + +sub bneg + { + # (BINT or num_str) return BINT + # negate number or make a negated number from string + my ($self,$x,$a,$p,$r) = objectify(1,@_); + return $x if $x->modify('bneg'); + # for +0 dont negate (to have always normalized) + return $x if $x->is_zero(); + $x->{sign} =~ tr/+\-/-+/; # does nothing for NaN + # $x->round($a,$p,$r); # changing this makes $x - $y modify $y!! + $x; + } + +sub bcmp + { + # Compares 2 values. Returns one of undef, <0, =0, >0. (suitable for sort) + # (BINT or num_str, BINT or num_str) return cond_code + my ($self,$x,$y) = objectify(2,@_); + return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + &cmp($x->{value},$y->{value},$x->{sign},$y->{sign}) <=> 0; + } + +sub bacmp + { + # Compares 2 values, ignoring their signs. + # Returns one of undef, <0, =0, >0. (suitable for sort) + # (BINT, BINT) return cond_code + my ($self,$x,$y) = objectify(2,@_); + return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + acmp($x->{value},$y->{value}) <=> 0; + } + +sub badd + { + # add second arg (BINT or string) to first (BINT) (modifies first) + # return result as BINT + trace(@_); + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + return $x if $x->modify('badd'); + return $x->bnan() if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + # for round calls, make array + my @bn = ($a,$p,$r,$y); + # speed: no add for 0+y or x+0 + return $x->bnorm(@bn) if $y->is_zero(); # x+0 + if ($x->is_zero()) # 0+y + { + # make copy, clobbering up x + $x->{value} = [ @{$y->{value}} ]; + $x->{sign} = $y->{sign} || $nan; + return $x->round(@bn); + } + + # shortcuts + my $xv = $x->{value}; + my $yv = $y->{value}; + my ($sx, $sy) = ( $x->{sign}, $y->{sign} ); # get signs + + if ($sx eq $sy) + { + add($xv,$yv); # if same sign, absolute add + $x->{sign} = $sx; + } + else + { + my $a = acmp ($yv,$xv); # absolute compare + if ($a > 0) + { + #print "swapped sub (a=$a)\n"; + &sub($yv,$xv,1); # absolute sub w/ swapped params + $x->{sign} = $sy; + } + elsif ($a == 0) + { + # speedup, if equal, set result to 0 + $x->{value} = [ 0 ]; + $x->{sign} = '+'; + } + else # a < 0 + { + #print "unswapped sub (a=$a)\n"; + &sub($xv, $yv); # absolute sub + $x->{sign} = $sx; } - $prod[$cty] += $car if $car; - $x = shift @prod; - } - ($signr, @x, @prod); -} - -# modulus -sub bmod { #(num_str, num_str) return num_str - (&bdiv(@_))[$[+1]; -} - -sub bdiv { #(dividend: num_str, divisor: num_str) return num_str - local (*x, *y); ($x, $y) = (&bnorm($_[$[]), &bnorm($_[$[+1])); - return wantarray ? ('NaN','NaN') : 'NaN' - if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0'); - return wantarray ? ('+0',$x) : '+0' if (&cmp(&abs($x),&abs($y)) < 0); - @x = &internal($x); @y = &internal($y); - $srem = $y[$[]; - $sr = (shift @x ne shift @y) ? '-' : '+'; - $car = $bar = $prd = 0; - if (($dd = int(1e5/($y[$#y]+1))) != 1) { - for $x (@x) { - $x = $x * $dd + $car; - if ($use_mult) { - $x -= ($car = int($x * 1e-5)) * 1e5; - } - else { - $x -= ($car = int($x / 1e5)) * 1e5; - } - } - push(@x, $car); $car = 0; - for $y (@y) { - $y = $y * $dd + $car; - if ($use_mult) { - $y -= ($car = int($y * 1e-5)) * 1e5; - } - else { - $y -= ($car = int($y / 1e5)) * 1e5; - } - } } - else { - push(@x, 0); - } - @q = (); ($v2,$v1) = @y[-2,-1]; - $v2 = 0 unless $v2; - while ($#x > $#y) { - ($u2,$u1,$u0) = @x[-3..-1]; - $u2 = 0 unless $u2; - $q = (($u0 == $v1) ? 99999 : int(($u0*1e5+$u1)/$v1)); - --$q while ($v2*$q > ($u0*1e5+$u1-$q*$v1)*1e5+$u2); - if ($q) { - ($car, $bar) = (0,0); - for ($y = $[, $x = $#x-$#y+$[-1; $y <= $#y; ++$y,++$x) { - $prd = $q * $y[$y] + $car; - if ($use_mult) { - $prd -= ($car = int($prd * 1e-5)) * 1e5; - } - else { - $prd -= ($car = int($prd / 1e5)) * 1e5; - } - $x[$x] += 1e5 if ($bar = (($x[$x] -= $prd + $bar) < 0)); - } - if ($x[$#x] < $car + $bar) { - $car = 0; --$q; - for ($y = $[, $x = $#x-$#y+$[-1; $y <= $#y; ++$y,++$x) { - $x[$x] -= 1e5 - if ($car = (($x[$x] += $y[$y] + $car) > 1e5)); - } - } - } - pop(@x); unshift(@q, $q); - } - if (wantarray) { - @d = (); - if ($dd != 1) { - $car = 0; - for $x (reverse @x) { - $prd = $car * 1e5 + $x; - $car = $prd - ($tmp = int($prd / $dd)) * $dd; - unshift(@d, $tmp); - } - } - else { - @d = @x; - } - (&external($sr, @q), &external($srem, @d, $zero)); - } else { - &external($sr, @q); - } -} - -# compute power of two numbers -- stolen from Knuth Vol 2 pg 233 -sub bpow { #(num_str, num_str) return num_str - local(*x, *y); ($x, $y) = (&bnorm($_[$[]), &bnorm($_[$[+1])); - if ($x eq 'NaN') { - 'NaN'; - } elsif ($y eq 'NaN') { - 'NaN'; - } elsif ($x eq '+1') { - '+1'; - } elsif ($x eq '-1') { - &bmod($x,2) ? '-1': '+1'; - } elsif ($y =~ /^-/) { - 'NaN'; - } elsif ($x eq '+0' && $y eq '+0') { - 'NaN'; - } else { - @x = &internal($x); - local(@pow2)=@x; - local(@pow)=&internal("+1"); - local($y1,$res,@tmp1,@tmp2)=(1); # need tmp to send to mul - while ($y ne '+0') { - ($y,$res)=&bdiv($y,2); - if ($res ne '+0') {@tmp=@pow2; @pow=&mul(*pow,*tmp);} - if ($y ne '+0') {@tmp=@pow2;@pow2=&mul(*pow2,*tmp);} - } - &external(@pow); - } -} - -# compute x << y, y >= 0 -sub blsft { #(num_str, num_str) return num_str - &bmul($_[$[], &bpow(2, $_[$[+1])); -} - -# compute x >> y, y >= 0 -sub brsft { #(num_str, num_str) return num_str - &bdiv($_[$[], &bpow(2, $_[$[+1])); -} - -# compute x & y -sub band { #(num_str, num_str) return num_str - local($x,$y,$r,$m,$xr,$yr) = (&bnorm($_[$[]),&bnorm($_[$[+1]),0,1); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - while ($x ne '+0' && $y ne '+0') { - ($x, $xr) = &bdiv($x, 0x10000); - ($y, $yr) = &bdiv($y, 0x10000); - $r = &badd(&bmul(int $xr & $yr, $m), $r); - $m = &bmul($m, 0x10000); + return $x->round(@bn); + } + +sub bsub + { + # (BINT or num_str, BINT or num_str) return num_str + # subtract second arg from first, modify first + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + trace(@_); + return $x if $x->modify('bsub'); + $x->badd($y->bneg()); # badd does not leave internal zeros + $y->bneg(); # refix y, assumes no one reads $y in between + return $x->round($a,$p,$r,$y); + } + +sub binc + { + # increment arg by one + my ($self,$x,$a,$p,$r) = objectify(1,@_); + # my $x = shift; $x = $class->new($x) unless ref $x; my $self = ref($x); + trace(@_); + return $x if $x->modify('binc'); + $x->badd($self->_one())->round($a,$p,$r); + } + +sub bdec + { + # decrement arg by one + my ($self,$x,$a,$p,$r) = objectify(1,@_); + trace(@_); + return $x if $x->modify('bdec'); + $x->badd($self->_one('-'))->round($a,$p,$r); + } + +sub blcm + { + # (BINT or num_str, BINT or num_str) return BINT + # does not modify arguments, but returns new object + # Lowest Common Multiplicator + trace(@_); + + my ($self,@arg) = objectify(0,@_); + my $x = $self->new(shift @arg); + while (@arg) { $x = _lcm($x,shift @arg); } + $x; + } + +sub bgcd + { + # (BINT or num_str, BINT or num_str) return BINT + # does not modify arguments, but returns new object + # GCD -- Euclids algorithm, variant C (Knuth Vol 3, pg 341 ff) + trace(@_); + + my ($self,@arg) = objectify(0,@_); + my $x = $self->new(shift @arg); + while (@arg) + { + #$x = _gcd($x,shift @arg); last if $x->is_one(); # new fast, but is slower + $x = _gcd_old($x,shift @arg); last if $x->is_one(); # old, slow, but faster + } + $x; + } + +sub bmod + { + # modulus + # (BINT or num_str, BINT or num_str) return BINT + my ($self,$x,$y) = objectify(2,@_); + + return $x if $x->modify('bmod'); + (&bdiv($self,$x,$y))[1]; + } + +sub bnot + { + # (num_str or BINT) return BINT + # represent ~x as twos-complement number + my ($self,$x) = objectify(1,@_); + return $x if $x->modify('bnot'); + $x->bneg(); $x->bdec(); # was: bsub(-1,$x);, time it someday + $x; + } + +sub is_zero + { + # return true if arg (BINT or num_str) is zero (array '+', '0') + #my ($self,$x) = objectify(1,@_); + #trace(@_); + my $x = shift; $x = $class->new($x) unless ref $x; + return (@{$x->{value}} == 1) && ($x->{sign} eq '+') + && ($x->{value}->[0] == 0); + } + +sub is_nan + { + # return true if arg (BINT or num_str) is NaN + #my ($self,$x) = objectify(1,@_); + #trace(@_); + my $x = shift; $x = $class->new($x) unless ref $x; + return ($x->{sign} eq $nan); + } + +sub is_inf + { + # return true if arg (BINT or num_str) is +-inf + #my ($self,$x) = objectify(1,@_); + #trace(@_); + my $x = shift; $x = $class->new($x) unless ref $x; + my $sign = shift || ''; + + return $x->{sign} =~ /^[+-]inf/ if $sign eq ''; + return $x->{sign} =~ /^[$sign]inf/; + } + +sub is_one + { + # return true if arg (BINT or num_str) is +1 (array '+', '1') + # or -1 if signis given + #my ($self,$x) = objectify(1,@_); + my $x = shift; $x = $class->new($x) unless ref $x; + my $sign = shift || '+'; #$_[2] || '+'; + return (@{$x->{value}} == 1) && ($x->{sign} eq $sign) + && ($x->{value}->[0] == 1); + } + +sub is_odd + { + # return true when arg (BINT or num_str) is odd, false for even + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + return (($x->{sign} ne $nan) && ($x->{value}->[0] & 1)); + } + +sub is_even + { + # return true when arg (BINT or num_str) is even, false for odd + my $x = shift; $x = $class->new($x) unless ref $x; + #my ($self,$x) = objectify(1,@_); + return (($x->{sign} ne $nan) && (!($x->{value}->[0] & 1))); + } + +sub bmul + { + # multiply two numbers -- stolen from Knuth Vol 2 pg 233 + # (BINT or num_str, BINT or num_str) return BINT + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + #print "$self bmul $x ",ref($x)," $y ",ref($y),"\n"; + trace(@_); + return $x if $x->modify('bmul'); + return $x->bnan() if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); + + mul($x,$y); # do actual math + return $x->round($a,$p,$r,$y); + } + +sub bdiv + { + # (dividend: BINT or num_str, divisor: BINT or num_str) return + # (BINT,BINT) (quo,rem) or BINT (only rem) + trace(@_); + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + return $x if $x->modify('bdiv'); + + # NaN? + return wantarray ? ($x->bnan(),bnan()) : $x->bnan() + if ($x->{sign} eq $nan || $y->{sign} eq $nan || $y->is_zero()); + + # 0 / something + return wantarray ? ($x,$self->bzero()) : $x if $x->is_zero(); + + # Is $x in the interval [0, $y) ? + my $cmp = acmp($x->{value},$y->{value}); + if (($cmp < 0) and ($x->{sign} eq $y->{sign})) + { + return $x->bzero() unless wantarray; + my $t = $x->copy(); # make copy first, because $x->bzero() clobbers $x + return ($x->bzero(),$t); + } + elsif ($cmp == 0) + { + # shortcut, both are the same, so set to +/- 1 + $x->_one( ($x->{sign} ne $y->{sign} ? '-' : '+') ); + return $x unless wantarray; + return ($x,$self->bzero()); + } + + # calc new sign and in case $y == +/- 1, return $x + $x->{sign} = ($x->{sign} ne $y->{sign} ? '-' : '+'); + # check for / +-1 (cant use $y->is_one due to '-' + if ((@{$y->{value}} == 1) && ($y->{value}->[0] == 1)) + { + return wantarray ? ($x,$self->bzero()) : $x; + } + + # call div here + my $rem = $self->bzero(); + $rem->{sign} = $y->{sign}; + ($x->{value},$rem->{value}) = div($x->{value},$y->{value}); + # do not leave rest "-0"; + $rem->{sign} = '+' if (@{$rem->{value}} == 1) && ($rem->{value}->[0] == 0); + if (($x->{sign} eq '-') and (!$rem->is_zero())) + { + $x->bdec(); + } + $x->round($a,$p,$r,$y); + if (wantarray) + { + $rem->round($a,$p,$r,$x,$y); + return ($x,$y-$rem) if $x->{sign} eq '-'; # was $x,$rem + return ($x,$rem); + } + return $x; + } + +sub bpow + { + # (BINT or num_str, BINT or num_str) return BINT + # compute power of two numbers -- stolen from Knuth Vol 2 pg 233 + # modifies first argument + #trace(@_); + my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); + + return $x if $x->modify('bpow'); + + return $x->bnan() if $x->{sign} eq $nan || $y->{sign} eq $nan; + return $x->_one() if $y->is_zero(); + return $x if $x->is_one() || $y->is_one(); + if ($x->{sign} eq '-' && @{$x->{value}} == 1 && $x->{value}->[0] == 1) + { + # if $x == -1 and odd/even y => +1/-1 + return $y->is_odd() ? $x : $x->_set(1); # $x->babs() would work to + # my Casio FX-5500L has here a bug, -1 ** 2 is -1, but -1 * -1 is 1 LOL + } + # shortcut for $x ** 2 + if ($y->{sign} eq '+' && @{$y->{value}} == 1 && $y->{value}->[0] == 2) + { + return $x->bmul($x)->bround($a,$p,$r); + } + # 1 ** -y => 1 / (1**y), so do test for negative $y after above's clause + return $x->bnan() if $y->{sign} eq '-'; + return $x if $x->is_zero(); # 0**y => 0 (if not y <= 0) + + # tels: 10**x is special (actually 100**x etc is special, too) but not here + #if ((@{$x->{value}} == 1) && ($x->{value}->[0] == 10)) + # { + # # 10**2 + # my $yi = int($y); my $yi5 = int($yi/5); + # $x->{value} = []; + # my $v = $x->{value}; + # if ($yi5 > 0) + # { + # # $x->{value}->[$yi5-1] = 0; # pre-padd array (no use) + # for (my $i = 0; $i < $yi5; $i++) + # { + # $v->[$i] = 0; + # } + # } + # push @{$v}, int( '1'.'0' x ($yi % 5)); + # if ($x->{sign} eq '-') + # { + # $x->{sign} = $y->is_odd() ? '-' : '+'; # -10**2 = 100, -10**3 = -1000 + # } + # return $x; + # } + + # based on the assumption that shifting in base 10 is fast, and that bpow() + # works faster if numbers are small: we count trailing zeros (this step is + # O(1)..O(N), but in case of O(N) we save much more time), stripping them + # out of the multiplication, and add $count * $y zeros afterwards: + # 300 ** 3 == 300*300*300 == 3*3*3 . '0' x 2 * 3 == 27 . '0' x 6 + my $zeros = $x->_trailing_zeros(); + if ($zeros > 0) + { + $x->brsft($zeros,10); # remove zeros + $x->bpow($y); # recursion (will not branch into here again) + $zeros = $y * $zeros; # real number of zeros to add + $x->blsft($zeros,10); + return $x; + } + + my $pow2 = $self->_one(); + my $y1 = $class->new($y); + my ($res); + while (!$y1->is_one()) + { + #print "bpow: p2: $pow2 x: $x y: $y1 r: $res\n"; + #print "len ",$x->length(),"\n"; + ($y1,$res)=&bdiv($y1,2); + if (!$res->is_zero()) { &bmul($pow2,$x); } + if (!$y1->is_zero()) { &bmul($x,$x); } + } + #print "bpow: e p2: $pow2 x: $x y: $y1 r: $res\n"; + &bmul($x,$pow2) if (!$pow2->is_one()); + #print "bpow: e p2: $pow2 x: $x y: $y1 r: $res\n"; + return $x->round($a,$p,$r); + } + +sub blsft + { + # (BINT or num_str, BINT or num_str) return BINT + # compute x << y, base n, y >= 0 + my ($self,$x,$y,$n) = objectify(2,@_); + + return $x if $x->modify('blsft'); + return $x->bnan() if ($x->{sign} !~ /^[+-]$/ || $y->{sign} !~ /^[+-]$/); + + $n = 2 if !defined $n; return $x if $n == 0; + return $x->bnan() if $n < 0 || $y->{sign} eq '-'; + if ($n != 10) + { + $x->bmul( $self->bpow($n, $y) ); + } + else + { + # shortcut (faster) for shifting by 10) since we are in base 10eX + # multiples of 5: + my $src = scalar @{$x->{value}}; # source + my $len = $y->numify(); # shift-len as normal int + my $rem = $len % 5; # reminder to shift + my $dst = $src + int($len/5); # destination + + my $v = $x->{value}; # speed-up + my $vd; # further speedup + #print "src $src:",$v->[$src]||0," dst $dst:",$v->[$dst]||0," rem $rem\n"; + $v->[$src] = 0; # avoid first ||0 for speed + while ($src >= 0) + { + $vd = $v->[$src]; $vd = '00000'.$vd; + #print "s $src d $dst '$vd' "; + $vd = substr($vd,-5+$rem,5-$rem); + #print "'$vd' "; + $vd .= $src > 0 ? substr('00000'.$v->[$src-1],-5,$rem) : '0' x $rem; + #print "'$vd' "; + $vd = substr($vd,-5,5) if length($vd) > 5; + #print "'$vd'\n"; + $v->[$dst] = int($vd); + $dst--; $src--; + } + # set lowest parts to 0 + while ($dst >= 0) { $v->[$dst--] = 0; } + # fix spurios last zero element + splice @$v,-1 if $v->[-1] == 0; + #print "elems: "; my $i = 0; + #foreach (reverse @$v) { print "$i $_ "; $i++; } print "\n"; + # old way: $x->bmul( $self->bpow($n, $y) ); + } + return $x; + } + +sub brsft + { + # (BINT or num_str, BINT or num_str) return BINT + # compute x >> y, base n, y >= 0 + my ($self,$x,$y,$n) = objectify(2,@_); + + return $x if $x->modify('brsft'); + return $x->bnan() if ($x->{sign} !~ /^[+-]$/ || $y->{sign} !~ /^[+-]$/); + + $n = 2 if !defined $n; return $x->bnan() if $n <= 0 || $y->{sign} eq '-'; + if ($n != 10) + { + scalar bdiv($x, $self->bpow($n, $y)); + } + else + { + # shortcut (faster) for shifting by 10) + # multiples of 5: + my $dst = 0; # destination + my $src = $y->numify(); # as normal int + my $rem = $src % 5; # reminder to shift + $src = int($src / 5); # source + my $len = scalar @{$x->{value}} - $src; # elems to go + my $v = $x->{value}; # speed-up + if ($rem == 0) + { + splice (@$v,0,$src); # even faster, 38.4 => 39.3 + } + else + { + my $vd; + $v->[scalar @$v] = 0; # avoid || 0 test inside loop + while ($dst < $len) + { + $vd = '00000'.$v->[$src]; + #print "$dst $src '$vd' "; + $vd = substr($vd,-5,5-$rem); + #print "'$vd' "; + $src++; + $vd = substr('00000'.$v->[$src],-$rem,$rem) . $vd; + #print "'$vd1' "; + #print "'$vd'\n"; + $vd = substr($vd,-5,5) if length($vd) > 5; + $v->[$dst] = int($vd); + $dst++; + } + splice (@$v,$dst) if $dst > 0; # kill left-over array elems + pop @$v if $v->[-1] == 0; # kill last element + } # else rem == 0 + # old way: scalar bdiv($x, $self->bpow($n, $y)); + } + return $x; + } + +sub band + { + #(BINT or num_str, BINT or num_str) return BINT + # compute x & y + trace(@_); + my ($self,$x,$y) = objectify(2,@_); + + return $x if $x->modify('band'); + + return $x->bnan() if ($x->{sign} !~ /^[+-]$/ || $y->{sign} !~ /^[+-]$/); + return $x->bzero() if $y->is_zero(); + my $r = $self->bzero(); my $m = new Math::BigInt 1; my ($xr,$yr); + my $x10000 = new Math::BigInt (0x10000); + my $y1 = copy(ref($x),$y); # make copy + while (!$x->is_zero() && !$y1->is_zero()) + { + ($x, $xr) = bdiv($x, $x10000); + ($y1, $yr) = bdiv($y1, $x10000); + $r->badd( bmul( new Math::BigInt ( $xr->numify() & $yr->numify()), $m )); + $m->bmul($x10000); + } + $x = $r; + } + +sub bior + { + #(BINT or num_str, BINT or num_str) return BINT + # compute x | y + trace(@_); + my ($self,$x,$y) = objectify(2,@_); + + return $x if $x->modify('bior'); + + return $x->bnan() if ($x->{sign} !~ /^[+-]$/ || $y->{sign} !~ /^[+-]$/); + return $x if $y->is_zero(); + my $r = $self->bzero(); my $m = new Math::BigInt 1; my ($xr,$yr); + my $x10000 = new Math::BigInt (0x10000); + my $y1 = copy(ref($x),$y); # make copy + while (!$x->is_zero() || !$y1->is_zero()) + { + ($x, $xr) = bdiv($x,$x10000); + ($y1, $yr) = bdiv($y1,$x10000); + $r->badd( bmul( new Math::BigInt ( $xr->numify() | $yr->numify()), $m )); + $m->bmul($x10000); + } + $x = $r; + } + +sub bxor + { + #(BINT or num_str, BINT or num_str) return BINT + # compute x ^ y + my ($self,$x,$y) = objectify(2,@_); + + return $x if $x->modify('bxor'); + + return $x->bnan() if ($x->{sign} eq $nan || $y->{sign} eq $nan); + return $x if $y->is_zero(); + return $x->bzero() if $x == $y; # shortcut + my $r = $self->bzero(); my $m = new Math::BigInt 1; my ($xr,$yr); + my $x10000 = new Math::BigInt (0x10000); + my $y1 = copy(ref($x),$y); # make copy + while (!$x->is_zero() || !$y1->is_zero()) + { + ($x, $xr) = bdiv($x, $x10000); + ($y1, $yr) = bdiv($y1, $x10000); + $r->badd( bmul( new Math::BigInt ( $xr->numify() ^ $yr->numify()), $m )); + $m->bmul($x10000); + } + $x = $r; + } + +sub length + { + my ($self,$x) = objectify(1,@_); + + return (_digits($x->{value}), 0) if wantarray; + _digits($x->{value}); + } + +sub digit + { + # return the nth digit, negative values count backward + my $x = shift; + my $n = shift || 0; + + my $len = $x->length(); + + $n = $len+$n if $n < 0; # -1 last, -2 second-to-last + $n = abs($n); # if negatives are to big + $len--; $n = $len if $n > $len; # n to big? + + my $elem = int($n / 5); # which array element + my $digit = $n % 5; # which digit in this element + $elem = '0000'.$x->{value}->[$elem]; # get element padded with 0's + return substr($elem,-$digit-1,1); + } + +sub _trailing_zeros + { + # return the amount of trailing zeros in $x + my $x = shift; + $x = $class->new($x) unless ref $x; + + return 0 if $x->is_zero() || $x->is_nan(); + # check each array elem in _m for having 0 at end as long as elem == 0 + # Upon finding a elem != 0, stop + my $zeros = 0; my $elem; + foreach my $e (@{$x->{value}}) + { + if ($e != 0) + { + $elem = "$e"; # preserve x + $elem =~ s/.*?(0*$)/$1/; # strip anything not zero + $zeros *= 5; # elems * 5 + $zeros += CORE::length($elem); # count trailing zeros + last; # early out + } + $zeros ++; # real else branch: 50% slower! + } + return $zeros; + } + +sub bsqrt + { + my ($self,$x) = objectify(1,@_); + + return $x->bnan() if $x->{sign} =~ /\-|$nan/; # -x or NaN => NaN + return $x->bzero() if $x->is_zero(); # 0 => 0 + return $x if $x == 1; # 1 => 1 + + my $y = $x->copy(); # give us one more digit accur. + my $l = int($x->length()/2); + + $x->bzero(); + $x->binc(); # keep ref($x), but modify it + $x *= 10 ** $l; + + # print "x: $y guess $x\n"; + + my $last = $self->bzero(); + while ($last != $x) + { + $last = $x; + $x += $y / $x; + $x /= 2; + } + return $x; + } + +sub exponent + { + # return a copy of the exponent (here always 0, NaN or 1 for $m == 0) + my ($self,$x) = objectify(1,@_); + + return bnan() if $x->is_nan(); + my $e = $class->bzero(); + return $e->binc() if $x->is_zero(); + $e += $x->_trailing_zeros(); + return $e; + } + +sub mantissa + { + # return a copy of the mantissa (here always $self) + my ($self,$x) = objectify(1,@_); + + return bnan() if $x->is_nan(); + my $m = $x->copy(); + # that's inefficient + my $zeros = $m->_trailing_zeros(); + $m /= 10 ** $zeros if $zeros != 0; + return $m; + } + +sub parts + { + # return a copy of both the exponent and the mantissa (here 0 and self) + my $self = shift; + $self = $class->new($self) unless ref $self; + + return ($self->mantissa(),$self->exponent()); + } + +############################################################################## +# rounding functions + +sub bfround + { + # precision: round to the $Nth digit left (+$n) or right (-$n) from the '.' + # $n == 0 => round to integer + my $x = shift; $x = $class->new($x) unless ref $x; + my ($scale,$mode) = $x->_scale_p($precision,$rnd_mode,@_); + return $x if !defined $scale; # no-op + + # no-op for BigInts if $n <= 0 + return $x if $scale <= 0; + + $x->bround( $x->length()-$scale, $mode); + } + +sub _scan_for_nonzero + { + my $x = shift; + my $pad = shift; + + my $len = $x->length(); + return 0 if $len == 1; # '5' is trailed by invisible zeros + my $follow = $pad - 1; + return 0 if $follow > $len || $follow < 1; + #print "checking $x $r\n"; + # old, slow way checking string for non-zero characters + my $r = substr ("$x",-$follow); + return 1 if $r =~ /[^0]/; return 0; + + # faster way checking array contents; it is actually not faster (even in a + # rounding-only-shoutout, so I leave the simpler code in) + #my $rem = $follow % 5; my $div = $follow / 5; my $v = $x->{value}; + # pad with zeros and extract + #print "last part : ",'00000'.$v->[$div]," $rem = '"; + #print substr('00000'.$v->[$div],-$rem,5),"'\n"; + #my $r1 = substr ('00000'.$v->[$div],-$rem,5); + #print "$r1\n"; + #return 1 if $r1 =~ /[^0]/; + # + #for (my $j = $div-1; $j >= 0; $j --) + # { + # #print "part $v->[$j]\n"; + # return 1 if $v->[$j] != 0; + # } + #return 0; + } + +sub fround + { + # to make life easier for switch between MBF and MBI (autoload fxxx() + # like MBF does for bxxx()?) + my $x = shift; + return $x->bround(@_); + } + +sub bround + { + # accuracy: +$n preserve $n digits from left, + # -$n preserve $n digits from right (f.i. for 0.1234 style in MBF) + # no-op for $n == 0 + # and overwrite the rest with 0's, return normalized number + # do not return $x->bnorm(), but $x + my $x = shift; $x = $class->new($x) unless ref $x; + my ($scale,$mode) = $x->_scale_a($accuracy,$rnd_mode,@_); + return $x if !defined $scale; # no-op + + # print "MBI round: $x to $scale $mode\n"; + # -scale means what? tom? hullo? -$scale needed by MBF round, but what for? + return $x if $x->is_nan() || $x->is_zero() || $scale == 0; + + # we have fewer digits than we want to scale to + my $len = $x->length(); + # print "$len $scale\n"; + return $x if $len < abs($scale); + + # count of 0's to pad, from left (+) or right (-): 9 - +6 => 3, or |-6| => 6 + my ($pad,$digit_round,$digit_after); + $pad = $len - $scale; + $pad = abs($scale)+1 if $scale < 0; + $digit_round = '0'; $digit_round = $x->digit($pad) if $pad < $len; + $digit_after = '0'; $digit_after = $x->digit($pad-1) if $pad > 0; + # print "r $x: pos:$pad l:$len s:$scale r:$digit_round a:$digit_after m: $mode\n"; + + # in case of 01234 we round down, for 6789 up, and only in case 5 we look + # closer at the remaining digits of the original $x, remember decision + my $round_up = 1; # default round up + $round_up -- if + ($mode eq 'trunc') || # trunc by round down + ($digit_after =~ /[01234]/) || # round down anyway, + # 6789 => round up + ($digit_after eq '5') && # not 5000...0000 + ($x->_scan_for_nonzero($pad) == 0) && + ( + ($mode eq 'even') && ($digit_round =~ /[24680]/) || + ($mode eq 'odd') && ($digit_round =~ /[13579]/) || + ($mode eq '+inf') && ($x->{sign} eq '-') || + ($mode eq '-inf') && ($x->{sign} eq '+') || + ($mode eq 'zero') # round down if zero, sign adjusted below + ); + # allow rounding one place left of mantissa + #print "$pad $len $scale\n"; + # this is triggering warnings, and buggy for $scale < 0 + #if (-$scale != $len) + { + # split mantissa at $scale and then pad with zeros + my $s5 = int($pad / 5); + my $i = 0; + while ($i < $s5) + { + $x->{value}->[$i++] = 0; # replace with 5 x 0 + } + $x->{value}->[$s5] = '00000'.$x->{value}->[$s5]; # pad with 0 + my $rem = $pad % 5; # so much left over + if ($rem > 0) + { + #print "remainder $rem\n"; + #print "elem $x->{value}->[$s5]\n"; + substr($x->{value}->[$s5],-$rem,$rem) = '0' x $rem; # stamp w/ '0' + } + $x->{value}->[$s5] = int ($x->{value}->[$s5]); # str '05' => int '5' + } + if ($round_up) # what gave test above? + { + $pad = $len if $scale < 0; # tlr: whack 0.51=>1.0 + # modify $x in place, undef, undef to avoid rounding + $x->badd( Math::BigInt->new($x->{sign}.'1'.'0'x$pad), + undef,undef ); + # str creation much faster than 10 ** something + } + $x; + } + +sub bfloor + { + # return integer less or equal then number, since it is already integer, + # always returns $self + my ($self,$x,$a,$p,$r) = objectify(1,@_); + + # not needed: return $x if $x->modify('bfloor'); + + return $x->round($a,$p,$r); + } + +sub bceil + { + # return integer greater or equal then number, since it is already integer, + # always returns $self + my ($self,$x,$a,$p,$r) = objectify(1,@_); + + # not needed: return $x if $x->modify('bceil'); + + return $x->round($a,$p,$r); + } + +############################################################################## +# private stuff (internal use only) + +sub trace + { + # print out a number without using bstr (avoid deep recurse) for trace/debug + return unless $trace; + + my ($package,$file,$line,$sub) = caller(1); + print "'$sub' called from '$package' line $line:\n "; + + foreach my $x (@_) + { + if (!defined $x) + { + print "undef, "; next; + } + if (!ref($x)) + { + print "'$x' "; next; + } + next if (ref($x) ne "HASH"); + print "$x->{sign} "; + foreach (@{$x->{value}}) + { + print "$_ "; + } + print ", "; + } + print "\n"; + } + +sub _set + { + # internal set routine to set X fast to an integer value < [+-]100000 + my $self = shift; + my $wanted = shift || 0; + + $self->{sign} = $nan, return if $wanted !~ /^[+-]?[0-9]+$/; + $self->{sign} = '-'; $self->{sign} = '+' if $wanted >= 0; + $self->{value} = [ abs($wanted) ]; + return $self; + } + +sub _one + { + # internal speedup, set argument to 1, or create a +/- 1 + my $self = shift; + my $x = $self->bzero(); $x->{value} = [ 1 ]; $x->{sign} = shift || '+'; $x; + } + +sub _swap + { + # Overload will swap params if first one is no object ref so that the first + # one is always an object ref. In this case, third param is true. + # This routine is to overcome the effect of scalar,$object creating an object + # of the class of this package, instead of the second param $object. This + # happens inside overload, when the overload section of this package is + # inherited by sub classes. + # For overload cases (and this is used only there), we need to preserve the + # args, hence the copy(). + # You can override this method in a subclass, the overload section will call + # $object->_swap() to make sure it arrives at the proper subclass, with some + # exceptions like '+' and '-'. + + # object, (object|scalar) => preserve first and make copy + # scalar, object => swapped, re-swap and create new from first + # (using class of second object, not $class!!) + my $self = shift; # for override in subclass + #print "swap $self 0:$_[0] 1:$_[1] 2:$_[2]\n"; + if ($_[2]) + { + my $c = ref ($_[0]) || $class; # fallback $class should not happen + return ( $c->new($_[1]), $_[0] ); + } + else + { + return ( $_[0]->copy(), $_[1] ); + } + } + +sub objectify + { + # check for strings, if yes, return objects instead + + # the first argument is number of args objectify() should look at it will + # return $count+1 elements, the first will be a classname. This is because + # overloaded '""' calls bstr($object,undef,undef) and this would result in + # useless objects beeing created and thrown away. So we cannot simple loop + # over @_. If the given count is 0, all arguments will be used. + + # If the second arg is a ref, use it as class. + # If not, try to use it as classname, unless undef, then use $class + # (aka Math::BigInt). The latter shouldn't happen,though. + + # caller: gives us: + # $x->badd(1); => ref x, scalar y + # Class->badd(1,2); => classname x (scalar), scalar x, scalar y + # Class->badd( Class->(1),2); => classname x (scalar), ref x, scalar y + # Math::BigInt::badd(1,2); => scalar x, scalar y + # In the last case we check number of arguments to turn it silently into + # $class,1,2. (We can not take '1' as class ;o) + # badd($class,1) is not supported (it should, eventually, try to add undef) + # currently it tries 'Math::BigInt' + 1, which will not work. + + trace(@_); + my $count = abs(shift || 0); + + #print caller(),"\n"; + + my @a; # resulting array + if (ref $_[0]) + { + # okay, got object as first + $a[0] = ref $_[0]; + } + else + { + # nope, got 1,2 (Class->xxx(1) => Class,1 and not supported) + $a[0] = $class; + #print "@_\n"; sleep(1); + $a[0] = shift if $_[0] =~ /^[A-Z].*::/; # classname as first? + } + #print caller(),"\n"; + # print "Now in objectify, my class is today $a[0]\n"; + my $k; + if ($count == 0) + { + while (@_) + { + $k = shift; + if (!ref($k)) + { + $k = $a[0]->new($k); + } + elsif (ref($k) ne $a[0]) + { + # foreign object, try to convert to integer + $k->can('as_number') ? $k = $k->as_number() : $k = $a[0]->new($k); } - $r; - } -} - -# compute x | y -sub bior { #(num_str, num_str) return num_str - local($x,$y,$r,$m,$xr,$yr) = (&bnorm($_[$[]),&bnorm($_[$[+1]),0,1); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - while ($x ne '+0' || $y ne '+0') { - ($x, $xr) = &bdiv($x, 0x10000); - ($y, $yr) = &bdiv($y, 0x10000); - $r = &badd(&bmul(int $xr | $yr, $m), $r); - $m = &bmul($m, 0x10000); + push @a,$k; + } + } + else + { + while ($count > 0) + { + #print "$count\n"; + $count--; + $k = shift; + if (!ref($k)) + { + $k = $a[0]->new($k); + } + elsif (ref($k) ne $a[0]) + { + # foreign object, try to convert to integer + $k->can('as_number') ? $k = $k->as_number() : $k = $a[0]->new($k); } - $r; - } -} - -# compute x ^ y -sub bxor { #(num_str, num_str) return num_str - local($x,$y,$r,$m,$xr,$yr) = (&bnorm($_[$[]),&bnorm($_[$[+1]),0,1); - if ($x eq 'NaN' || $y eq 'NaN') { - 'NaN'; - } else { - while ($x ne '+0' || $y ne '+0') { - ($x, $xr) = &bdiv($x, 0x10000); - ($y, $yr) = &bdiv($y, 0x10000); - $r = &badd(&bmul(int $xr ^ $yr, $m), $r); - $m = &bmul($m, 0x10000); + push @a,$k; + } + push @a,@_; # return other params, too + } + #my $i = 0; + #foreach (@a) + # { + # print "o $i $a[0]\n" if $i == 0; + # print "o $i ",ref($_),"\n" if $i != 0; $i++; + # } + #print "objectify done: would return ",scalar @a," values\n"; + #print caller(1),"\n" unless wantarray; + die "$class objectify needs list context" unless wantarray; + @a; + } + +sub import + { + my $self = shift; + #print "import $self @_\n"; + for ( my $i = 0; $i < @_ ; $i++ ) + { + if ( $_[$i] eq ':constant' ) + { + # this rest causes overlord er load to step in + overload::constant integer => sub { $self->new(shift) }; + splice @_, $i, 1; last; + } + } + # any non :constant stuff is handled by our parent, Exporter + # even if @_ is empty, to give it a chance + #$self->SUPER::import(@_); # does not work + $self->export_to_level(1,$self,@_); # need this instead + } + +sub _internal + { + # (ref to self, ref to string) return ref to num_array + # Convert a number from string format to internal base 100000 format. + # Assumes normalized value as input. + my ($s,$d) = @_; + my $il = CORE::length($$d)-1; + # these leaves '00000' instead of int 0 and will be corrected after any op + $s->{value} = [ reverse(unpack("a" . ($il%5+1) . ("a5" x ($il/5)), $$d)) ]; + $s; + } + +sub _strip_zeros + { + # internal normalization function that strips leading zeros from the array + # args: ref to array + #trace(@_); + my $s = shift; + + my $cnt = scalar @$s; # get count of parts + my $i = $cnt-1; + #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) + while ($i > 0) { last if $s->[$i] != 0; $i--; } + $i++; splice @$s,$i if ($i < $cnt); # $i cant be 0 + return $s; + } + +sub _from_hex + { + # convert a (ref to) big hex string to BigInt, return undef for error + my $hs = shift; + + my $x = Math::BigInt->bzero(); + return $x->bnan() if $$hs !~ /^[\-\+]?0x[0-9A-Fa-f]+$/; + + my $mul = Math::BigInt->bzero(); $mul++; + my $x65536 = Math::BigInt->new(65536); + + my $len = CORE::length($$hs)-2; my $sign = '+'; + if ($$hs =~ /^\-/) + { + $sign = '-'; $len--; + } + $len = int($len/4); # 4-digit parts, w/o '0x' + my $val; my $i = -4; + while ($len >= 0) + { + $val = substr($$hs,$i,4); + $val =~ s/^[\-\+]?0x// if $len == 0; # for last part only because + $val = hex($val); # hex does not like wrong chars + # print "$val ",substr($$hs,$i,4),"\n"; + $i -= 4; $len --; + $x += $mul * $val if $val != 0; + $mul *= $x65536 if $len >= 0; # skip last mul + } + $x->{sign} = $sign if !$x->is_zero(); + return $x; + } + +sub _from_bin + { + # convert a (ref to) big binary string to BigInt, return undef for error + my $bs = shift; + + my $x = Math::BigInt->bzero(); + return $x->bnan() if $$bs !~ /^[\-\+]?0b[01]+$/; + + my $mul = Math::BigInt->bzero(); $mul++; + my $x256 = Math::BigInt->new(256); + + my $len = CORE::length($$bs)-2; my $sign = '+'; + if ($$bs =~ /^\-/) + { + $sign = '-'; $len--; + } + $len = int($len/8); # 8-digit parts, w/o '0b' + my $val; my $i = -8; + while ($len >= 0) + { + $val = substr($$bs,$i,8); + $val =~ s/^[\-\+]?0b// if $len == 0; # for last part only + #$val = oct('0b'.$val); # does not work on Perl prior 5.6.0 + $val = ('0' x (8-CORE::length($val))).$val if CORE::length($val) < 8; + $val = ord(pack('B8',$val)); + # print "$val ",substr($$bs,$i,16),"\n"; + $i -= 8; $len --; + $x += $mul * $val if $val != 0; + $mul *= $x256 if $len >= 0; # skip last mul + } + $x->{sign} = $sign if !$x->is_zero(); + return $x; + } + +sub _split + { + # (ref to num_str) return num_str + # internal, take apart a string and return the pieces + my $x = shift; + + # pre-parse input + $$x =~ s/^\s+//g; # strip white space at front + $$x =~ s/\s+$//g; # strip white space at end + #$$x =~ s/\s+//g; # strip white space (no longer) + return if $$x eq ""; + + return _from_hex($x) if $$x =~ /^[\-\+]?0x/; # hex string + return _from_bin($x) if $$x =~ /^[\-\+]?0b/; # binary string + + return if $$x !~ /^[\-\+]?\.?[0-9]/; + + $$x =~ s/(\d)_(\d)/$1$2/g; # strip underscores between digits + $$x =~ s/(\d)_(\d)/$1$2/g; # do twice for 1_2_3 + + # some possible inputs: + # 2.1234 # 0.12 # 1 # 1E1 # 2.134E1 # 434E-10 # 1.02009E-2 + # .2 # 1_2_3.4_5_6 # 1.4E1_2_3 # 1e3 # +.2 + + #print "input: '$$x' "; + my ($m,$e) = split /[Ee]/,$$x; + $e = '0' if !defined $e || $e eq ""; + # print "m '$m' e '$e'\n"; + # sign,value for exponent,mantint,mantfrac + my ($es,$ev,$mis,$miv,$mfv); + # valid exponent? + if ($e =~ /^([+-]?)0*(\d+)$/) # strip leading zeros + { + $es = $1; $ev = $2; + #print "'$m' '$e' e: $es $ev "; + # valid mantissa? + return if $m eq '.' || $m eq ''; + my ($mi,$mf) = split /\./,$m; + $mi = '0' if !defined $mi; + $mi .= '0' if $mi =~ /^[\-\+]?$/; + $mf = '0' if !defined $mf || $mf eq ''; + if ($mi =~ /^([+-]?)0*(\d+)$/) # strip leading zeros + { + $mis = $1||'+'; $miv = $2; + #print "$mis $miv"; + # valid, existing fraction part of mantissa? + return unless ($mf =~ /^(\d*?)0*$/); # strip trailing zeros + $mfv = $1; + #print " split: $mis $miv . $mfv E $es $ev\n"; + return (\$mis,\$miv,\$mfv,\$es,\$ev); + } + } + return; # NaN, not a number + } + +sub _digits + { + # computer number of digits in bigint, minus the sign + # int() because add/sub leaves sometimes strings (like '00005') instead of + # int ('5') in this place, causing length to fail + my $cx = shift; + + #print "len: ",(@$cx-1)*5+CORE::length(int($cx->[-1])),"\n"; + return (@$cx-1)*5+CORE::length(int($cx->[-1])); + } + +sub as_number + { + # an object might be asked to return itself as bigint on certain overloaded + # operations, this does exactly this, so that sub classes can simple inherit + # it or override with their own integer conversion routine + my $self = shift; + + return $self->copy(); + } + +############################################################################## +# internal calculation routines + +sub acmp + { + # internal absolute post-normalized compare (ignore signs) + # ref to array, ref to array, return <0, 0, >0 + # arrays must have at least on entry, this is not checked for + + my ($cx, $cy) = @_; + + #print "$cx $cy\n"; + my ($i,$a,$x,$y,$k); + # calculate length based on digits, not parts + $x = _digits($cx); $y = _digits($cy); + # print "length: ",($x-$y),"\n"; + return $x-$y if ($x - $y); # if different in length + #print "full compare\n"; + $i = 0; $a = 0; + # first way takes 5.49 sec instead of 4.87, but has the early out advantage + # so grep is slightly faster, but more unflexible. hm. $_ instead if $k + # yields 5.6 instead of 5.5 sec huh? + # manual way (abort if unequal, good for early ne) + my $j = scalar @$cx - 1; + while ($j >= 0) + { + # print "$cx->[$j] $cy->[$j] $a",$cx->[$j]-$cy->[$j],"\n"; + last if ($a = $cx->[$j] - $cy->[$j]); $j--; + } + return $a; + # while it early aborts, it is even slower than the manual variant + #grep { return $a if ($a = $_ - $cy->[$i++]); } @$cx; + # grep way, go trough all (bad for early ne) + #grep { $a = $_ - $cy->[$i++]; } @$cx; + #return $a; + } + +sub cmp + { + # post-normalized compare for internal use (honors signs) + # ref to array, ref to array, return < 0, 0, >0 + my ($cx,$cy,$sx,$sy) = @_; + + #return 0 if (is0($cx,$sx) && is0($cy,$sy)); + + if ($sx eq '+') + { + return 1 if $sy eq '-'; # 0 check handled above + return acmp($cx,$cy); + } + else + { + # $sx eq '-' + return -1 if ($sy eq '+'); + return acmp($cy,$cx); + } + return 0; # equal + } + +sub add + { + # (ref to int_num_array, ref to int_num_array) + # routine to add two base 1e5 numbers + # stolen from Knuth Vol 2 Algorithm A pg 231 + # there are separate routines to add and sub as per Kunth pg 233 + # This routine clobbers up array x, but not y. + + my ($x,$y) = @_; + + # 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, $j could be made integer + # but this would impose a limit to number-length to 2**32. + my $i; my $car = 0; my $j = 0; + for $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++; + } + } + +sub sub + { + # (ref to int_num_array, ref to int_num_array) + # subtract base 1e5 numbers -- stolen from Knuth Vol 2 pg 232, $x > $y + # subtract Y from X (X is always greater/equal!) by modifiyng x in place + my ($sx,$sy,$s) = @_; + + my $car = 0; my $i; my $j = 0; + if (!$s) + { + #print "case 2\n"; + for $i (@$sx) + { + last unless defined $sy->[$j] || $car; + #print "x: $i y: $sy->[$j] c: $car\n"; + $i += $BASE if $car = (($i -= ($sy->[$j] || 0) + $car) < 0); $j++; + #print "x: $i y: $sy->[$j-1] c: $car\n"; + } + # might leave leading zeros, so fix that + _strip_zeros($sx); + return $sx; + } + else + { + #print "case 1 (swap)\n"; + for $i (@$sx) + { + last unless defined $sy->[$j] || $car; + #print "$sy->[$j] $i $car => $sx->[$j]\n"; + $sy->[$j] += $BASE + if $car = (($sy->[$j] = $i-($sy->[$j]||0) - $car) < 0); + #print "$sy->[$j] $i $car => $sy->[$j]\n"; + $j++; + } + # might leave leading zeros, so fix that + _strip_zeros($sy); + return $sy; + } + } + +sub mul + { + # (BINT, BINT) return nothing + # multiply two numbers in internal representation + # modifies first arg, second needs not be different from first + my ($x,$y) = @_; + + $x->{sign} = $x->{sign} ne $y->{sign} ? '-' : '+'; + my @prod = (); my ($prod,$car,$cty,$xi,$yi); + my $xv = $x->{value}; + my $yv = $y->{value}; + # since multiplying $x with $x fails, make copy in this case + $yv = [@$xv] if "$xv" eq "$yv"; + for $xi (@$xv) + { + $car = 0; $cty = 0; + for $yi (@$yv) + { + $prod = $xi * $yi + ($prod[$cty] || 0) + $car; + $prod[$cty++] = + $prod - ($car = int($prod * 1e-5)) * $BASE; # see USE_MUL + } + $prod[$cty] += $car if $car; # need really to check for 0? + $xi = shift @prod; + } + push @$xv, @prod; + _strip_zeros($x->{value}); + # normalize (handled last to save check for $y->is_zero() + $x->{sign} = '+' if @$xv == 1 && $xv->[0] == 0; # not is_zero due to '-' + } + +sub div + { + # ref to array, ref to array, modify first array and return reminder if + # in list context + # does no longer handle sign + my ($x,$yorg) = @_; + my ($car,$bar,$prd,$dd,$xi,$yi,@q,$v2,$v1); + + my (@d,$tmp,$q,$u2,$u1,$u0); + + $car = $bar = $prd = 0; + + my $y = [ @$yorg ]; + if (($dd = int($BASE/($y->[-1]+1))) != 1) + { + for $xi (@$x) + { + $xi = $xi * $dd + $car; + $xi -= ($car = int($xi * $RBASE)) * $BASE; # see USE_MUL + } + push(@$x, $car); $car = 0; + for $yi (@$y) + { + $yi = $yi * $dd + $car; + $yi -= ($car = int($yi * $RBASE)) * $BASE; # see USE_MUL + } + } + else + { + push(@$x, 0); + } + @q = (); ($v2,$v1) = @$y[-2,-1]; + $v2 = 0 unless $v2; + while ($#$x > $#$y) + { + ($u2,$u1,$u0) = @$x[-3..-1]; + $u2 = 0 unless $u2; + print "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n" + if $v1 == 0; + $q = (($u0 == $v1) ? 99999 : int(($u0*$BASE+$u1)/$v1)); + --$q while ($v2*$q > ($u0*1e5+$u1-$q*$v1)*$BASE+$u2); + if ($q) + { + ($car, $bar) = (0,0); + for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi) + { + $prd = $q * $y->[$yi] + $car; + $prd -= ($car = int($prd * $RBASE)) * $BASE; # see USE_MUL + $x->[$xi] += 1e5 if ($bar = (($x->[$xi] -= $prd + $bar) < 0)); } - $r; + if ($x->[-1] < $car + $bar) + { + $car = 0; --$q; + for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi) + { + $x->[$xi] -= 1e5 + if ($car = (($x->[$xi] += $y->[$yi] + $car) > $BASE)); + } + } + } + pop(@$x); unshift(@q, $q); } -} + if (wantarray) + { + @d = (); + if ($dd != 1) + { + $car = 0; + for $xi (reverse @$x) + { + $prd = $car * $BASE + $xi; + $car = $prd - ($tmp = int($prd / $dd)) * $dd; # see USE_MUL + unshift(@d, $tmp); + } + } + else + { + @d = @$x; + } + @$x = @q; + _strip_zeros($x); + _strip_zeros(\@d); + return ($x,\@d); + } + @$x = @q; + _strip_zeros($x); + return $x; + } + +sub _lcm + { + # (BINT or num_str, BINT or num_str) return BINT + # does modify first argument + # LCM + + my $x = shift; my $ty = shift; + return $x->bnan() if ($x->{sign} eq $nan) || ($ty->{sign} eq $nan); + return $x * $ty / bgcd($x,$ty); + } + +sub _gcd_old + { + # (BINT or num_str, BINT or num_str) return BINT + # does modify first arg + # GCD -- Euclids algorithm E, Knuth Vol 2 pg 296 + trace(@_); + + my $x = shift; my $ty = $class->new(shift); # preserve y + return $x->bnan() if ($x->{sign} eq $nan) || ($ty->{sign} eq $nan); + + while (!$ty->is_zero()) + { + ($x, $ty) = ($ty,bmod($x,$ty)); + } + $x; + } + +sub _gcd + { + # (BINT or num_str, BINT or num_str) return BINT + # does not modify arguments + # GCD -- Euclids algorithm, variant L (Lehmer), Knuth Vol 3 pg 347 ff + # unfortunately, it is slower and also seems buggy (the A=0, B=1, C=1, D=0 + # case..) + trace(@_); + + my $u = $class->new(shift); my $v = $class->new(shift); # preserve u,v + return $u->bnan() if ($u->{sign} eq $nan) || ($v->{sign} eq $nan); + + $u->babs(); $v->babs(); # Euclid is valid for |u| and |v| + + my ($U,$V,$A,$B,$C,$D,$T,$Q); # single precision variables + my ($t); # multiprecision variables + + while ($v > $BASE) + { + #sleep 1; + ($u,$v) = ($v,$u) if ($u < $v); # make sure that u >= v + #print "gcd: $u $v\n"; + # step L1, initialize + $A = 1; $B = 0; $C = 0; $D = 1; + $U = $u->{value}->[-1]; # leading digits of u + $V = $v->{value}->[-1]; # leading digits of v + + # step L2, test quotient + if (($V + $C != 0) && ($V + $D != 0)) # div by zero => go to L4 + { + $Q = int(($U + $A)/($V + $C)); # quotient + #print "L1 A=$A B=$B C=$C D=$D U=$U V=$V Q=$Q\n"; + # do L3? (div by zero => go to L4) + while ($Q == int(($U + $B)/($V + $D))) + { + # step L3, emulate Euclid + #print "L3a A=$A B=$B C=$C D=$D U=$U V=$V Q=$Q\n"; + $T = $A - $Q*$C; $A = $C; $C = $T; + $T = $B - $Q*$D; $B = $D; $D = $T; + $T = $U - $Q*$V; $U = $V; $V = $T; + last if ($V + $D == 0) || ($V + $C == 0); # div by zero => L4 + $Q = int(($U + $A)/($V + $C)); # quotient for next test + #print "L3b A=$A B=$B C=$C D=$D U=$U V=$V Q=$Q\n"; + } + } + # step L4, multiprecision step + # was if ($B == 0) + # in case A = 0, B = 1, C = 0 and D = 1, this case would simple swap u & v + # and loop endless. Not sure why this happens, Knuth does not make a + # remark about this special case. bug? + if (($B == 0) || (($A == 0) && ($C == 1) && ($D == 0))) + { + #print "L4b1: u=$u v=$v\n"; + ($u,$v) = ($v,bmod($u,$v)); + #$t = $u % $v; $u = $v->copy(); $v = $t; + #print "L4b12: u=$u v=$v\n"; + } + else + { + #print "L4b: $u $v $A $B $C $D\n"; + $t = $A*$u + $B*$v; $v *= $D; $v += $C*$u; $u = $t; + #print "L4b2: $u $v\n"; + } + } # back to L1 -# represent ~x as twos-complement number -sub bnot { #(num_str) return num_str - &bsub(-1,$_[$[]); -} + return _gcd_old($u,$v) if $v != 1; # v too small + return $v; # 1 + } + +############################################################################### +# this method return 0 if the object can be modified, or 1 for not +# We use a fast use constant statement here, to avoid costly calls. Subclasses +# may override it with special code (f.i. Math::BigInt::Constant does so) + +use constant modify => 0; + +#sub modify +# { +# my $self = shift; +# my $method = shift; +# print "original $self modify by $method\n"; +# return 0; # $self; +# } 1; __END__ @@ -446,84 +2140,514 @@ Math::BigInt - Arbitrary size integer math package =head1 SYNOPSIS use Math::BigInt; - $i = Math::BigInt->new($string); - - $i->bneg return BINT negation - $i->babs return BINT absolute value - $i->bcmp(BINT) return CODE compare numbers (undef,<0,=0,>0) - $i->badd(BINT) return BINT addition - $i->bsub(BINT) return BINT subtraction - $i->bmul(BINT) return BINT multiplication - $i->bdiv(BINT) return (BINT,BINT) division (quo,rem) just quo if scalar - $i->bmod(BINT) return BINT modulus - $i->bgcd(BINT) return BINT greatest common divisor - $i->bnorm return BINT normalization - $i->blsft(BINT) return BINT left shift - $i->brsft(BINT) return (BINT,BINT) right shift (quo,rem) just quo if scalar - $i->band(BINT) return BINT bit-wise and - $i->bior(BINT) return BINT bit-wise inclusive or - $i->bxor(BINT) return BINT bit-wise exclusive or - $i->bnot return BINT bit-wise not + + # Number creation + $x = Math::BigInt->new($str); # defaults to 0 + $nan = Math::BigInt->bnan(); # create a NotANumber + $zero = Math::BigInt->bzero();# create a "+0" + + # Testing + $x->is_zero(); # return whether arg is zero or not + $x->is_nan(); # return whether arg is NaN or not + $x->is_one(); # return true if arg is +1 + $x->is_one('-'); # return true if arg is -1 + $x->is_odd(); # return true if odd, false for even + $x->is_even(); # return true if even, false for odd + $x->bcmp($y); # compare numbers (undef,<0,=0,>0) + $x->bacmp($y); # compare absolutely (undef,<0,=0,>0) + $x->sign(); # return the sign, either +,- or NaN + $x->digit($n); # return the nth digit, counting from right + $x->digit(-$n); # return the nth digit, counting from left + + # The following all modify their first argument: + + # set + $x->bzero(); # set $x to 0 + $x->bnan(); # set $x to NaN + + $x->bneg(); # negation + $x->babs(); # absolute value + $x->bnorm(); # normalize (no-op) + $x->bnot(); # two's complement (bit wise not) + $x->binc(); # increment x by 1 + $x->bdec(); # decrement x by 1 + + $x->badd($y); # addition (add $y to $x) + $x->bsub($y); # subtraction (subtract $y from $x) + $x->bmul($y); # multiplication (multiply $x by $y) + $x->bdiv($y); # divide, set $x to quotient + # return (quo,rem) or quo if scalar + + $x->bmod($y); # modulus (x % y) + $x->bpow($y); # power of arguments (x ** y) + $x->blsft($y); # left shift + $x->brsft($y); # right shift + $x->blsft($y,$n); # left shift, by base $n (like 10) + $x->brsft($y,$n); # right shift, by base $n (like 10) + + $x->band($y); # bitwise and + $x->bior($y); # bitwise inclusive or + $x->bxor($y); # bitwise exclusive or + $x->bnot(); # bitwise not (two's complement) + + $x->bsqrt(); # calculate square-root + + $x->round($A,$P,$round_mode); # round to accuracy or precision using mode $r + $x->bround($N); # accuracy: preserve $N digits + $x->bfround($N); # round to $Nth digit, no-op for BigInts + + # The following do not modify their arguments in BigInt, but do in BigFloat: + $x->bfloor(); # return integer less or equal than $x + $x->bceil(); # return integer greater or equal than $x + + # The following do not modify their arguments: + + bgcd(@values); # greatest common divisor + blcm(@values); # lowest common multiplicator + + $x->bstr(); # normalized string + $x->bsstr(); # normalized string in scientific notation + $x->length(); # return number of digits in number + ($x,$f) = $x->length(); # length of number and length of fraction part + + $x->exponent(); # return exponent as BigInt + $x->mantissa(); # return mantissa as BigInt + $x->parts(); # return (mantissa,exponent) as BigInt =head1 DESCRIPTION -All basic math operations are overloaded if you declare your big -integers as +All operators (inlcuding basic math operations) are overloaded if you +declare your big integers as - $i = new Math::BigInt '123 456 789 123 456 789'; + $i = new Math::BigInt '123_456_789_123_456_789'; +Operations with overloaded operators preserve the arguments which is +exactly what you expect. =over 2 =item Canonical notation -Big integer value are strings of the form C</^[+-]\d+$/> with leading +Big integer values are strings of the form C</^[+-]\d+$/> with leading zeros suppressed. + '-0' canonical value '-0', normalized '0' + ' -123_123_123' canonical value '-123123123' + '1_23_456_7890' canonical value '1234567890' + =item Input -Input values to these routines may be strings of the form -C</^\s*[+-]?[\d\s]+$/>. +Input values to these routines may be either Math::BigInt objects or +strings of the form C</^\s*[+-]?[\d]+\.?[\d]*E?[+-]?[\d]*$/>. + +You can include one underscore between any two digits. + +This means integer values like 1.01E2 or even 1000E-2 are also accepted. +Non integer values result in NaN. + +Math::BigInt::new() defaults to 0, while Math::BigInt::new('') results +in 'NaN'. + +bnorm() on a BigInt object is now effectively a no-op, since the numbers +are always stored in normalized form. On a string, it creates a BigInt +object. =item Output -Output values always always in canonical form +Output values are BigInt objects (normalized), except for bstr(), which +returns a string in normalized form. +Some routines (C<is_odd()>, C<is_even()>, C<is_zero()>, C<is_one()>, +C<is_nan()>) return true or false, while others (C<bcmp()>, C<bacmp()>) +return either undef, <0, 0 or >0 and are suited for sort. =back -Actual math is done in an internal format consisting of an array -whose first element is the sign (/^[+-]$/) and whose remaining -elements are base 100000 digits with the least significant digit first. -The string 'NaN' is used to represent the result when input arguments -are not numbers, as well as the result of dividing by zero. +=head2 Rounding -=head1 EXAMPLES +Only C<bround()> is defined for BigInts, for further details of rounding see +L<Math::BigFloat>. + +=over 2 - '+0' canonical zero value - ' -123 123 123' canonical value '-123123123' - '1 23 456 7890' canonical value '+1234567890' +=item bfround ( +$scale ) rounds to the $scale'th place left from the '.' +=item bround ( +$scale ) preserves accuracy to $scale sighnificant digits counted from the left and paddes the number with zeros + +=item bround ( -$scale ) preserves accuracy to $scale significant digits counted from the right and paddes the number with zeros. + +=back + +C<bfround()> does nothing in case of negative C<$scale>. Both C<bround()> and +C<bfround()> are a no-ops for a scale of 0. + +All rounding functions take as a second parameter a rounding mode from one of +the following: 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'. + +The default is 'even'. By using C<< Math::BigInt->round_mode($rnd_mode); >> +you can get and set the default round mode for subsequent rounding. + +The second parameter to the round functions than overrides the default +temporarily. + +=head2 Internals + +Actual math is done in an internal format consisting of an array of +elements of base 100000 digits with the least significant digit first. +The sign C</^[+-]$/> is stored separately. The string 'NaN' is used to +represent the result when input arguments are not numbers, as well as +the result of dividing by zero. + +You sould neither care nor depend on the internal represantation, it might +change without notice. Use only method calls like C<< $x->sign(); >> instead +relying on the internal hash keys like in C<< $x->{sign}; >>. + +=head2 mantissa(), exponent() and parts() + +C<mantissa()> and C<exponent()> return the said parts of the BigInt such +that: + + $m = $x->mantissa(); + $e = $x->exponent(); + $y = $m * ( 10 ** $e ); + print "ok\n" if $x == $y; + +C<($m,$e) = $x->parts()> is just a shortcut that gives you both of them in one +go. Both the returned mantissa and exponent do have a sign. + +Currently, for BigInts C<$e> will be always 0, except for NaN where it will be +NaN and for $x == 0, then it will be 1 (to be compatible with Math::BigFlaot's +internal representation of a zero as C<0E1>). + +C<$m> will always be a copy of the original number. The relation between $e +and $m might change in the future, but will be always equivalent in a +numerical sense, e.g. $m might get minized. + +=head1 EXAMPLES + + use Math::BigInt qw(bstr bint); + $x = bstr("1234") # string "1234" + $x = "$x"; # same as bstr() + $x = bneg("1234") # Bigint "-1234" + $x = Math::BigInt->bneg("1234"); # Bigint "-1234" + $x = Math::BigInt->babs("-12345"); # Bigint "12345" + $x = Math::BigInt->bnorm("-0 00"); # BigInt "0" + $x = bint(1) + bint(2); # BigInt "3" + $x = bint(1) + "2"; # ditto (auto-BigIntify of "2") + $x = bint(1); # BigInt "1" + $x = $x + 5 / 2; # BigInt "3" + $x = $x ** 3; # BigInt "27" + $x *= 2; # BigInt "54" + $x = new Math::BigInt; # BigInt "0" + $x--; # BigInt "-1" + $x = Math::BigInt->badd(4,5) # BigInt "9" + $x = Math::BigInt::badd(4,5) # BigInt "9" + print $x->bsstr(); # 9e+0 =head1 Autocreating constants -After C<use Math::BigInt ':constant'> all the integer decimal constants -in the given scope are converted to C<Math::BigInt>. This conversion +After C<use Math::BigInt ':constant'> all the B<integer> decimal constants +in the given scope are converted to C<Math::BigInt>. This conversion happens at compile time. In particular - perl -MMath::BigInt=:constant -e 'print 2**100' + perl -MMath::BigInt=:constant -e 'print 2**100,"\n"' + +prints the integer value of C<2**100>. Note that without conversion of +constants the expression 2**100 will be calculated as floating point +number. + +Please note that strings and floating point constants are not affected, +so that + + use Math::BigInt qw/:constant/; + + $x = 1234567890123456789012345678901234567890 + + 123456789123456789; + $x = '1234567890123456789012345678901234567890' + + '123456789123456789'; -print the integer value of C<2**100>. Note that without conversion of -constants the expression 2**100 will be calculated as floating point number. +do both not work. You need a explicit Math::BigInt->new() around one of them. + +=head1 PERFORMANCE + +Using the form $x += $y; etc over $x = $x + $y is faster, since a copy of $x +must be made in the second case. For long numbers, the copy can eat up to 20% +of the work (in case of addition/subtraction, less for +multiplication/division). If $y is very small compared to $x, the form +$x += $y is MUCH faster than $x = $x + $y since making the copy of $x takes +more time then the actual addition. + +With a technic called copy-on-write the cost of copying with overload could +be minimized or even completely avoided. This is currently not implemented. + +The new version of this module is slower on new(), bstr() and numify(). Some +operations may be slower for small numbers, but are significantly faster for +big numbers. Other operations are now constant (O(1), like bneg(), babs() +etc), instead of O(N) and thus nearly always take much less time. + +For more benchmark results see http://bloodgate.com/perl/benchmarks.html =head1 BUGS -The current version of this module is a preliminary version of the -real thing that is currently (as of perl5.002) under development. +=over 2 + +=item :constant and eval() + +Under Perl prior to 5.6.0 having an C<use Math::BigInt ':constant';> and +C<eval()> in your code will crash with "Out of memory". This is probably an +overload/exporter bug. You can workaround by not having C<eval()> +and ':constant' at the same time or upgrade your Perl. + +=back + +=head1 CAVEATS + +Some things might not work as you expect them. Below is documented what is +known to be troublesome: + +=over 1 + +=item stringify, bstr(), bsstr() and 'cmp' + +Both stringify and bstr() now drop the leading '+'. The old code would return +'+3', the new returns '3'. This is to be consistent with Perl and to make +cmp (especially with overloading) to work as you expect. It also solves +problems with Test.pm, it's ok() uses 'eq' internally. + +Mark said, when asked about to drop the '+' altogether, or make only cmp work: + + I agree (with the first alternative), don't add the '+' on positive + numbers. It's not as important anymore with the new internal + form for numbers. It made doing things like abs and neg easier, + but those have to be done differently now anyway. + +So, the following examples will now work all as expected: + + use Test; + BEGIN { plan tests => 1 } + use Math::BigInt; + + my $x = new Math::BigInt 3*3; + my $y = new Math::BigInt 3*3; + + ok ($x,3*3); + print "$x eq 9" if $x eq $y; + print "$x eq 9" if $x eq '9'; + print "$x eq 9" if $x eq 3*3; + +Additionally, the following still works: + + print "$x == 9" if $x == $y; + print "$x == 9" if $x == 9; + print "$x == 9" if $x == 3*3; + +There is now a C<bsstr()> method to get the string in scientific notation aka +C<1e+2> instead of C<100>. Be advised that overloaded 'eq' always uses bstr() +for comparisation, but Perl will represent some numbers as 100 and others +as 1e+308. If in doubt, convert both arguments to Math::BigInt before doing eq: + + use Test; + BEGIN { plan tests => 3 } + use Math::BigInt; + + $x = Math::BigInt->new('1e56'); $y = 1e56; + ok ($x,$y); # will fail + ok ($x->bsstr(),$y); # okay + $y = Math::BigInt->new($y); + ok ($x,$y); # okay + +=item int() + +C<int()> will return (at least for Perl v5.7.1 and up) another BigInt, not a +Perl scalar: + + $x = Math::BigInt->new(123); + $y = int($x); # BigInt 123 + $x = Math::BigFloat->new(123.45); + $y = int($x); # BigInt 123 + +In all Perl versions you can use C<as_number()> for the same effect: + + $x = Math::BigFloat->new(123.45); + $y = $x->as_number(); # BigInt 123 + +This also works for other subclasses, like Math::String. + +=item bdiv + +The following will probably not do what you expect: + + print $c->bdiv(10000),"\n"; + +It prints both quotient and reminder since print calls C<bdiv()> in list +context. Also, C<bdiv()> will modify $c, so be carefull. You probably want +to use + + print $c / 10000,"\n"; + print scalar $c->bdiv(10000),"\n"; # or if you want to modify $c + +instead. + +The quotient is always the greatest integer less than or equal to the +real-valued quotient of the two operands, and the remainder (when it is +nonzero) always has the same sign as the second operand; so, for +example, + + 1 / 4 => ( 0, 1) + 1 / -4 => (-1,-3) + -3 / 4 => (-1, 1) + -3 / -4 => ( 0,-3) + +As a consequence, the behavior of the operator % agrees with the +behavior of Perl's built-in % operator (as documented in the perlop +manpage), and the equation + + $x == ($x / $y) * $y + ($x % $y) + +holds true for any $x and $y, which justifies calling the two return +values of bdiv() the quotient and remainder. + +Perl's 'use integer;' changes the behaviour of % and / for scalars, but will +not change BigInt's way to do things. This is because under 'use integer' Perl +will do what the underlying C thinks is right and this is different for each +system. If you need BigInt's behaving exactly like Perl's 'use integer', bug +the author to implement it ;) + +=item Modifying and = + +Beware of: + + $x = Math::BigFloat->new(5); + $y = $x; + +It will not do what you think, e.g. making a copy of $x. Instead it just makes +a second reference to the B<same> object and stores it in $y. Thus anything +that modifies $x will modify $y, and vice versa. + + $x->bmul(2); + print "$x, $y\n"; # prints '10, 10' + +If you want a true copy of $x, use: + + $y = $x->copy(); + +See also the documentation in L<overload> regarding C<=>. + +=item bpow + +C<bpow()> (and the rounding functions) now modifies the first argument and +return it, unlike the old code which left it alone and only returned the +result. This is to be consistent with C<badd()> etc. The first three will +modify $x, the last one won't: + + print bpow($x,$i),"\n"; # modify $x + print $x->bpow($i),"\n"; # ditto + print $x **= $i,"\n"; # the same + print $x ** $i,"\n"; # leave $x alone + +The form C<$x **= $y> is faster than C<$x = $x ** $y;>, though. + +=item Overloading -$x + +The following: + + $x = -$x; + +is slower than + + $x->bneg(); + +since overload calls C<sub($x,0,1);> instead of C<neg($x)>. The first variant +needs to preserve $x since it does not know that it later will get overwritten. +This makes a copy of $x and takes O(N). But $x->bneg() is O(1). + +With Copy-On-Write, this issue will be gone. Stay tuned... + +=item Mixing different object types + +In Perl you will get a floating point value if you do one of the following: + + $float = 5.0 + 2; + $float = 2 + 5.0; + $float = 5 / 2; + +With overloaded math, only the first two variants will result in a BigFloat: + + use Math::BigInt; + use Math::BigFloat; + + $mbf = Math::BigFloat->new(5); + $mbi2 = Math::BigInteger->new(5); + $mbi = Math::BigInteger->new(2); + + # what actually gets called: + $float = $mbf + $mbi; # $mbf->badd() + $float = $mbf / $mbi; # $mbf->bdiv() + $integer = $mbi + $mbf; # $mbi->badd() + $integer = $mbi2 / $mbi; # $mbi2->bdiv() + $integer = $mbi2 / $mbf; # $mbi2->bdiv() + +This is because math with overloaded operators follows the first (dominating) +operand, this one's operation is called and returns thus the result. So, +Math::BigInt::bdiv() will always return a Math::BigInt, regardless whether +the result should be a Math::BigFloat or the second operant is one. + +To get a Math::BigFloat you either need to call the operation manually, +make sure the operands are already of the proper type or casted to that type +via Math::BigFloat->new(): + + $float = Math::BigFloat->new($mbi2) / $mbi; # = 2.5 + +Beware of simple "casting" the entire expression, this would only convert +the already computed result: + + $float = Math::BigFloat->new($mbi2 / $mbi); # = 2.0 thus wrong! + +Beware of the order of more complicated expressions like: + + $integer = ($mbi2 + $mbi) / $mbf; # int / float => int + $integer = $mbi2 / Math::BigFloat->new($mbi); # ditto + +If in doubt, break the expression into simpler terms, or cast all operands +to the desired resulting type. + +Scalar values are a bit different, since: + + $float = 2 + $mbf; + $float = $mbf + 2; + +will both result in the proper type due to the way the overloaded math works. + +This section also applies to other overloaded math packages, like Math::String. + +=item bsqrt() + +C<bsqrt()> works only good if the result is an big integer, e.g. the square +root of 144 is 12, but from 12 the square root is 3, regardless of rounding +mode. + +If you want a better approximation of the square root, then use: + + $x = Math::BigFloat->new(12); + $Math::BigFloat::precision = 0; + Math::BigFloat->round_mode('even'); + print $x->copy->bsqrt(),"\n"; # 4 + + $Math::BigFloat::precision = 2; + print $x->bsqrt(),"\n"; # 3.46 + print $x->bsqrt(3),"\n"; # 3.464 + +=back + +=head1 LICENSE + +This program is free software; you may redistribute it and/or modify it under +the same terms as Perl itself. -=head1 AUTHOR +=head1 AUTHORS -Mark Biggar, overloaded interface by Ilya Zakharevich. +Original code by Mark Biggar, overloaded interface by Ilya Zakharevich. +Completely rewritten by Tels http://bloodgate.com in late 2000, 2001. =cut |