package
sanity::BaseCalc;
our
$AUTHORITY
=
'cpan:BBYRD'
;
our
$VERSION
=
'1.03'
;
Math::BigInt ->config({
upgrade
=>
'Math::BigFloat'
,
round_mode
=>
'common'
,
trap_nan
=> 1,
trap_inf
=> 1,
});
Math::BigFloat->config({
round_mode
=>
'common'
,
trap_nan
=> 1,
trap_inf
=> 1,
});
sub
new {
my
(
$pack
,
%opts
) =
@_
;
my
$self
=
bless
{},
$pack
;
$self
->{neg_char} =
$opts
{neg_char} ||
'-'
;
$self
->{radix_char} =
$opts
{radix_char} ||
'.'
;
$opts
{digits} =
$_
[1]
if
(
@_
== 2);
$self
->digits(
$opts
{digits});
return
$self
;
}
sub
digits {
my
$self
=
shift
;
return
@{
$self
->{digits}}
unless
(
@_
);
if
(
ref
$_
[0] eq
'ARRAY'
) {
$self
->{digits} = [ @{
shift
() } ];
delete
$self
->{digitset_name};
}
else
{
my
$name
=
shift
;
my
%digitsets
=
$self
->_digitsets;
croak
"Unrecognized digit set '$name'"
unless
exists
$digitsets
{
$name
};
$self
->{digits} =
$digitsets
{
$name
};
$self
->{digitset_name} =
$name
;
}
$self
->{neg_char} =
''
if
(
grep
{
$_
eq
$self
->{neg_char} } @{
$self
->{digits}});
$self
->{radix_char} =
''
if
(
grep
{
$_
eq
$self
->{radix_char} } @{
$self
->{digits}});
$self
->{digit_strength} =
log
(
scalar
@{
$self
->{digits}}) /
log
(10);
delete
$self
->{trans};
@{
$self
->{trans}}{@{
$self
->{digits}}} = 0..$
return
@{
$self
->{digits}};
}
sub
_digitsets {
return
(
'bin'
=> [0,1],
'hex'
=> [0..9,
'a'
..
'f'
],
'HEX'
=> [0..9,
'A'
..
'F'
],
'oct'
=> [0..7],
'64'
=> [
'A'
..
'Z'
,
'a'
..
'z'
,0..9,
'+'
,
'/'
],
'62'
=> [0..9,
'a'
..
'z'
,
'A'
..
'Z'
],
);
}
sub
from_base {
my
(
$self
,
$str
) =
@_
;
my
(
$nc
,
$fc
) =
@$self
{
qw(neg_char radix_char)
};
return
$self
->_from_accurate_return( Math::BigFloat->new(
$self
->from_base(
$str
) )->bneg() )
if
$nc
&&
$str
=~ s/^\Q
$nc
\E//;
my
$base
= @{
$self
->{digits}};
my
$zero
=
$self
->{digits}[0];
my
$is_dec
= (
$fc
&&
$str
=~ /\Q
$fc
\E/);
$str
=~ s/^\Q
$zero
\E+//;
$str
=~ s/\Q
$zero
\E+$//
if
(
$is_dec
);
my
$poten_digits
=
int
(
length
(
$str
) *
$self
->{digit_strength}) + 16;
Math::BigFloat->accuracy(
$poten_digits
+ 16);
my
$result
= Math::BigFloat->new(0);
$result
=
$result
->as_int()
unless
$is_dec
;
unless
(
$is_dec
|| !
$self
->{digitset_name}) {
$result
=
$result
->from_hex(
lc
"0x$str"
)
if
(
$self
->{digitset_name} =~ /^
hex
$/i);
$result
=
$result
->from_bin(
"0b$str"
)
if
(
$self
->{digitset_name} eq
'bin'
);
$result
=
$result
->from_oct(
lc
"0$str"
)
if
(
$self
->{digitset_name} eq
'oct'
);
}
if
(
$result
== 0) {
my
$i
= 0;
$i
=
length
(
$str
)- 1;
$i
=
length
($1) - 1
if
(
$fc
&&
$str
=~ s/^(.*)\Q
$fc
\E(.*)$/$1$2/);
while
(
$str
=~ s/^(.)// ) {
my
$v
=
$self
->{trans}{$1};
croak
"Invalid character $1 in string!"
unless
defined
$v
;
my
$exp
= Math::BigInt->new(
$base
);
$result
=
$exp
->bpow(
$i
)->bmul(
$v
)->badd(
$result
);
$i
--;
}
}
return
$self
->_from_accurate_return(
$result
);
}
sub
_from_accurate_return {
my
(
$self
,
$result
) =
@_
;
my
$rscalar
=
$result
->numify();
my
$rstring
=
$result
->bstr();
$rstring
=~ s/0+$//
if
(
$rstring
=~ /\./);
$rstring
=~ s/\.$//;
return
$rstring
eq (
$rscalar
+ 0 .
''
) ?
$result
->numify() :
$rstring
;
}
sub
to_base {
my
(
$self
,
$num
) =
@_
;
my
$base
=
scalar
@{
$self
->{digits}};
my
$is_dec
= (
$num
=~ /\./) ? 1 : 0;
$is_dec
= 0
unless
$self
->{radix_char};
my
$zero
=
$self
->{digits}[0];
my
$poten_digits
=
length
(
$num
);
Math::BigFloat->accuracy(
$poten_digits
+ 16);
$num
= Math::BigFloat->new(
$num
);
$num
=
$num
->as_int()
unless
$is_dec
&&
$self
->{radix_char};
return
$self
->{neg_char}.
$self
->to_base(
$num
->bneg )
if
$num
< 0;
return
$zero
if
(
$num
== 0);
unless
(
$is_dec
|| !
$self
->{digitset_name}) {
return
substr
(
lc
$num
->as_hex(), 2)
if
(
$self
->{digitset_name} eq
'hex'
);
return
substr
(
uc
$num
->as_hex(), 2)
if
(
$self
->{digitset_name} eq
'HEX'
);
return
substr
(
$num
->as_bin(), 2)
if
(
$self
->{digitset_name} eq
'bin'
);
return
substr
(
$num
->as_oct(), 1)
if
(
$self
->{digitset_name} eq
'oct'
);
}
my
$i
=
$num
->copy()->blog(
$base
,
int
(
$num
->
length
() / 9) + 2
)->bfloor()->numify();
my
$result
=
''
;
while
((
$num
!= 0 ||
$i
>= 0) &&
$i
> -1024) {
my
$exp
= Math::BigFloat->new(
$base
);
$exp
=
$i
< 0 ?
$exp
->bpow(
$i
) :
$exp
->as_int->bpow(
$i
);
my
$v
=
$num
->copy()->bdiv(
$exp
)->bfloor();
$num
-=
$v
*
$exp
;
$result
.=
$self
->{radix_char}
if
(
$i
== -1);
$result
.=
$self
->{digits}[
$v
];
$i
--;
}
return
$zero
unless
length
$result
;
$result
=~ s/^\Q
$zero
\E+//;
$result
=~ s/\Q
$zero
\E+$//
if
(
$is_dec
);
return
$result
;
}
1;