package Math::Bacovia;
use 5.014;
use strict;
use warnings;
BEGIN {
$Math::Bacovia::VERSION = '0.04';
}
use List::UtilsBy qw();
use Class::Multimethods qw();
use Math::AnyNum qw();
our %HIERARCHY = (
'Math::Bacovia::Number' => 0,
'Math::Bacovia::Difference' => 1,
'Math::Bacovia::Fraction' => 2,
'Math::Bacovia::Power' => 3,
'Math::Bacovia::Log' => 4,
'Math::Bacovia::Exp' => 5,
'Math::Bacovia::Sum' => 6,
'Math::Bacovia::Product' => 7,
'Math::Bacovia::Symbol' => 8,
);
use Math::Bacovia::Exp;
use Math::Bacovia::Log;
use Math::Bacovia::Power;
use Math::Bacovia::Fraction;
use Math::Bacovia::Difference;
use Math::Bacovia::Number;
use Math::Bacovia::Sum;
use Math::Bacovia::Product;
use Math::Bacovia::Symbol;
our ($MONE, $ZERO, $ONE, $TWO, $HALF);
{
my $mone = 'Math::AnyNum'->mone;
my $zero = 'Math::AnyNum'->zero;
my $one = 'Math::AnyNum'->one;
$MONE = 'Math::Bacovia::Number'->new($mone);
$ZERO = 'Math::Bacovia::Number'->new($zero);
$ONE = 'Math::Bacovia::Number'->new($one);
$TWO = 'Math::Bacovia::Number'->new($one + $one);
$HALF = 'Math::Bacovia::Number'->new($one / ($one + $one));
};
sub _check_type ($) {
my ($ref) = @_;
if (my $r = ref($$ref)) {
if ($r eq 'Math::AnyNum') {
$$ref = 'Math::Bacovia::Number'->new($$ref);
}
elsif (UNIVERSAL::isa($r, 'Math::Bacovia')) {
## ok
}
else {
$$ref = 'Math::Bacovia::Number'->new($$ref);
}
}
elsif (defined($$ref)) {
$$ref = 'Math::Bacovia::Number'->new($$ref);
}
else {
$$ref = $ZERO;
}
}
use overload
'""' => sub { $_[0]->stringify },
'0+' => sub { $_[0]->numeric },
'==' => sub {
my ($x, $y) = @_;
Math::Bacovia::_check_type(\$y);
$x->eq($y);
},
'!=' => sub {
my ($x, $y) = @_;
Math::Bacovia::_check_type(\$y);
!($x->eq($y));
},
'++' => sub { $_[0]->add($ONE) },
'--' => sub { $_[0]->sub($ONE) },
'+' => sub {
my ($x, $y, $s) = @_;
Math::Bacovia::_check_type(\$x);
Math::Bacovia::_check_type(\$y);
$s ? $y->add($x) : $x->add($y);
},
'-' => sub {
my ($x, $y, $s) = @_;
Math::Bacovia::_check_type(\$x);
Math::Bacovia::_check_type(\$y);
$s ? $y->sub($x) : $x->sub($y);
},
'*' => sub {
my ($x, $y, $s) = @_;
Math::Bacovia::_check_type(\$x);
Math::Bacovia::_check_type(\$y);
$s ? $y->mul($x) : $x->mul($y);
},
'/' => sub {
my ($x, $y, $s) = @_;
Math::Bacovia::_check_type(\$x);
Math::Bacovia::_check_type(\$y);
$s ? $y->div($x) : $x->div($y);
},
'**' => sub {
my ($x, $y, $s) = @_;
Math::Bacovia::_check_type(\$x);
Math::Bacovia::_check_type(\$y);
$s ? $y->pow($x) : $x->pow($y);
},
eq => sub { "$_[0]" eq "$_[1]" },
ne => sub { "$_[0]" ne "$_[1]" },
cmp => sub { $_[2] ? "$_[1]" cmp $_[0]->stringify : $_[0]->stringify cmp "$_[1]" },
neg => sub { $_[0]->neg },
sin => \&sin,
cos => \&cos,
exp => \&exp,
log => \&log,
int => \&int,
atan2 => \&atan2,
#abs => \&abs,
sqrt => \&sqrt;
#
## Import/export
#
sub i () {
'Math::Bacovia::Number'->new('Math::AnyNum'->i);
}
sub pi () {
'Math::Bacovia::Log'->new($MONE) * -i;
}
sub tau () {
'Math::Bacovia::Log'->new($MONE) * -(i + i);
}
sub e () {
'Math::Bacovia::Exp'->new($ONE);
}
my %exported_functions = (
Exp => \&_exp,
Log => \&_log,
Product => \&_product,
Sum => \&_sum,
Power => \&_power,
Symbol => \&_symbol,
Number => \&_number,
Fraction => \&_fraction,
Difference => \&_difference,
);
my %exported_constants = (
i => \&i,
pi => \&pi,
tau => \&tau,
e => \&e,
);
sub import {
shift;
my $caller = caller(0);
while (@_) {
my $name = shift(@_);
if ($name eq ':all') {
push @_, keys(%exported_functions), keys(%exported_constants);
next;
}
no strict 'refs';
my $caller_sub = $caller . '::' . $name;
if (exists($exported_functions{$name})) {
my $sub = $exported_functions{$name};
*$caller_sub = $exported_functions{$name};
}
elsif (exists($exported_constants{$name})) {
my $value = $exported_constants{$name}->();
*$caller_sub = sub() { $value };
}
else {
die "unknown import: <<$name>>";
}
}
return;
}
sub _log {
'Math::Bacovia::Log'->new(@_);
}
sub _exp {
'Math::Bacovia::Exp'->new(@_);
}
sub _fraction {
'Math::Bacovia::Fraction'->new(@_);
}
sub _difference {
'Math::Bacovia::Difference'->new(@_);
}
sub _power {
'Math::Bacovia::Power'->new(@_);
}
sub _sum {
'Math::Bacovia::Sum'->new(@_);
}
sub _product {
'Math::Bacovia::Product'->new(@_);
}
sub _symbol {
'Math::Bacovia::Symbol'->new(@_);
}
sub _number {
'Math::Bacovia::Number'->new(@_);
}
#
## BASIC OPERATIONS
#
Class::Multimethods::multimethod add => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Sum'->new($x, $y);
};
Class::Multimethods::multimethod sub => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Difference'->new($x, $y);
};
Class::Multimethods::multimethod mul => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Product'->new($x, $y);
};
Class::Multimethods::multimethod div => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Fraction'->new($x, $y);
};
Class::Multimethods::multimethod pow => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Power'->new($x, $y);
};
Class::Multimethods::multimethod add => (__PACKAGE__, '*') => sub {
my ($x, $y) = @_;
$x + 'Math::Bacovia::Number'->new($y);
};
Class::Multimethods::multimethod sub => (__PACKAGE__, '*') => sub {
my ($x, $y) = @_;
$x - 'Math::Bacovia::Number'->new($y);
};
Class::Multimethods::multimethod mul => (__PACKAGE__, '*') => sub {
my ($x, $y) = @_;
$x * 'Math::Bacovia::Number'->new($y);
};
Class::Multimethods::multimethod div => (__PACKAGE__, '*') => sub {
my ($x, $y) = @_;
$x / 'Math::Bacovia::Number'->new($y);
};
Class::Multimethods::multimethod pow => (__PACKAGE__, '*') => sub {
my ($x, $y) = @_;
$x**('Math::Bacovia::Number'->new($y));
};
Class::Multimethods::multimethod add => ('*', __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Number'->new($x) + $y;
};
Class::Multimethods::multimethod sub => ('*', __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Number'->new($x) - $y;
};
Class::Multimethods::multimethod mul => ('*', __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Number'->new($x) * $y;
};
Class::Multimethods::multimethod div => ('*', __PACKAGE__) => sub {
my ($x, $y) = @_;
'Math::Bacovia::Number'->new($x) / $y;
};
Class::Multimethods::multimethod pow => ('*', __PACKAGE__) => sub {
my ($x, $y) = @_;
('Math::Bacovia::Number'->new($x))**$y;
};
sub int {
my ($x) = @_;
$x->{_int} //= CORE::int($x->numeric);
}
sub sqrt {
my ($x) = @_;
$x->{_sqrt} //= $x**$HALF;
}
sub log {
my ($x) = @_;
$x->{_log} //= 'Math::Bacovia::Log'->new($x);
}
sub exp {
my ($x) = @_;
$x->{_exp} //= 'Math::Bacovia::Exp'->new($x);
}
sub neg {
my ($x) = @_;
$x->{_neg} //= 'Math::Bacovia::Difference'->new($ZERO, $x);
}
sub inv {
my ($x) = @_;
$x->{_inv} //= 'Math::Bacovia::Fraction'->new($ONE, $x);
}
#
## TRIGONOMETRIC FUNCTIONS
#
# sin(x) = (exp(x * i) - exp(-i * x))/(2*i)
sub sin {
my ($x) = @_;
$x->{_sin} //=
'Math::Bacovia::Fraction'->new('Math::Bacovia::Exp'->new(i * $x) - 'Math::Bacovia::Exp'->new(-i * $x), $TWO * i);
}
# asin(x) = -i * log(i x + sqrt(1 - x^2))
sub asin {
my ($x) = @_;
$x->{_asin} //= -i * 'Math::Bacovia::Log'->new(i * $x + &sqrt($ONE - $x**$TWO));
}
# sinh(x) = (exp(2x) - 1) / (2*exp(x))
sub sinh {
my ($x) = @_;
$x->{_sinh} //=
'Math::Bacovia::Fraction'->new(('Math::Bacovia::Exp'->new($TWO * $x) - $ONE), ($TWO * 'Math::Bacovia::Exp'->new($x)));
}
# asinh(x) = log(sqrt(x^2 + 1) + x)
sub asinh {
my ($x) = @_;
$x->{_asinh} //= 'Math::Bacovia::Log'->new(&sqrt($x**$TWO + $ONE) + $x);
}
# cos(x) = (exp(-i*x) + exp(i*x)) / 2
sub cos {
my ($x) = @_;
$x->{_cos} //=
'Math::Bacovia::Fraction'->new('Math::Bacovia::Exp'->new(-i * $x) + 'Math::Bacovia::Exp'->new(i * $x), $TWO);
}
# acos(x) = π/2 + i log(i x + sqrt(1 - x^2))
# acos(x) = -2*i*log(i*sqrt((1 - x)/2) + sqrt((1 + x)/2))
sub acos {
my ($x) = @_;
#$x->{_acos} //= 'Math::Bacovia::Log'->new(i) * -i + i * 'Math::Bacovia::Log'->new(i * $x + &sqrt($ONE - $x**$TWO));
$x->{_acos} //= -i * $TWO * 'Math::Bacovia::Log'->new(&sqrt(($ONE - $x) / $TWO) * i + &sqrt(($ONE + $x) / $TWO));
}
# cosh(x) = (exp(2x) + 1) / (2*exp(x))
sub cosh {
my ($x) = @_;
#<<<
$x->{_cosh} //= 'Math::Bacovia::Fraction'->new(('Math::Bacovia::Exp'->new($TWO * $x) + $ONE), ($TWO * 'Math::Bacovia::Exp'->new($x)));
#>>>
}
# acosh(x) = log(x + sqrt(x - 1) * sqrt(x + 1))
sub acosh {
my ($x) = @_;
$x->{_acosh} //= 'Math::Bacovia::Log'->new($x + &sqrt($x - $ONE) * &sqrt($x + $ONE));
}
# tan(x) = -i + (2*i)/(1 + exp(2*i*x))
sub tan {
my ($x) = @_;
$x->{_tan} //= -i + 'Math::Bacovia::Fraction'->new($TWO * i, $ONE + 'Math::Bacovia::Exp'->new($TWO * i * $x));
}
# atan(x) = i * (log(1 - i*x) - log(1 + i*x)) / 2
# atan(x) = log(sqrt(1 - i*x) / sqrt(1 + i*x)) * i
sub atan {
my ($x) = @_;
#<<<
#$x->{_atan} //= i * 'Math::Bacovia::Fraction'->new('Math::Bacovia::Log'->new($ONE - i * $x) - 'Math::Bacovia::Log'->new($ONE + i * $x), $TWO);
$x->{_atan} //= 'Math::Bacovia::Log'->new('Math::Bacovia::Fraction'->new(&sqrt($ONE - i*$x), &sqrt($ONE + i*$x))) * i;
#>>>
}
# atan2(x, y) = -i * log((y + x*i) / sqrt(x^2 + y^2))
sub atan2 {
my ($x, $y) = @_;
$x->{_atan2} //= 'Math::Bacovia::Log'->new('Math::Bacovia::Fraction'->new($y + $x * i, &sqrt($x**$TWO + $y**$TWO))) * -i;
}
# tanh(x) = (exp(2x) - 1) / (exp(2x) + 1)
sub tanh {
my ($x) = @_;
$x->{_tanh} //= 'Math::Bacovia::Fraction'->new(('Math::Bacovia::Exp'->new($TWO * $x) - $ONE),
('Math::Bacovia::Exp'->new($TWO * $x) + $ONE));
}
# atanh(x) = (log(1 + x) - log(1 - x)) / 2 = log(sqrt(1+x) / sqrt(1-x))
sub atanh {
my ($x) = @_;
$x->{_atanh} //=
#'Math::Bacovia::Fraction'->new('Math::Bacovia::Log'->new($ONE + $x) - 'Math::Bacovia::Log'->new($ONE - $x), $TWO);
'Math::Bacovia::Log'->new(
'Math::Bacovia::Fraction'->new(
'Math::Bacovia::Power'->new($ONE + $x, $HALF),
'Math::Bacovia::Power'->new($ONE - $x, $HALF)
)
);
}
# cot(x) = i + (2*i)/(exp(2*i*x) - 1)
sub cot {
my ($x) = @_;
$x->{_cot} //= i + 'Math::Bacovia::Fraction'->new($TWO * i, 'Math::Bacovia::Exp'->new($TWO * i * $x) - $ONE);
}
# acot(x) = atan(1/x)
sub acot {
my ($x) = @_;
$x->{_acot} //= $x->inv->atan;
}
# coth(x) = (exp(2x) + 1) / (exp(2x) - 1)
sub coth {
my ($x) = @_;
$x->{_coth} //= 'Math::Bacovia::Fraction'->new(('Math::Bacovia::Exp'->new($TWO * $x) + $ONE),
('Math::Bacovia::Exp'->new($TWO * $x) - $ONE));
}
# acoth(x) = atanh(1/x)
sub acoth {
my ($x) = @_;
$x->{_acoth} //= $x->inv->atanh;
}
# sec(x) = 2/(exp(-i*x) + exp(i*x))
sub sec {
my ($x) = @_;
$x->{_sec} //=
'Math::Bacovia::Fraction'->new($TWO, 'Math::Bacovia::Exp'->new(-i * $x) + 'Math::Bacovia::Exp'->new(i * $x));
}
# asec(x) = acos(1/x)
sub asec {
my ($x) = @_;
$x->{_asec} //= $x->inv->acos;
}
# sech(x) = (2*exp(x)) / (exp(2x) + 1)
sub sech {
my ($x) = @_;
$x->{_sech} //=
'Math::Bacovia::Fraction'->new(($TWO * 'Math::Bacovia::Exp'->new($x)), ('Math::Bacovia::Exp'->new($TWO * $x) + $ONE));
}
# asech(x) = acosh(1/x)
sub asech {
my ($x) = @_;
$x->{_asech} //= $x->inv->acosh;
}
# csc(x) = -(2*i)/(exp(-i*x) - exp(i*x))
sub csc {
my ($x) = @_;
$x->{_csc} //=
'Math::Bacovia::Fraction'->new(-$TWO * i, 'Math::Bacovia::Exp'->new(-i * $x) - 'Math::Bacovia::Exp'->new(i * $x));
}
# acsc(x) = asin(1/x)
sub acsc {
my ($x) = @_;
$x->{_acsc} //= $x->inv->asin;
}
# csch(x) = (2*exp(x)) / (exp(2x) - 1)
sub csch {
my ($x) = @_;
$x->{_csch} //=
'Math::Bacovia::Fraction'->new(($TWO * 'Math::Bacovia::Exp'->new($x)), ('Math::Bacovia::Exp'->new($TWO * $x) - $ONE));
}
# acsch(x) = asinh(1/x)
sub acsch {
my ($x) = @_;
$x->{_acsch} //= $x->inv->asinh;
}
sub simple {
my ($x, %opt) = @_;
$x->{_simple} //= ((List::UtilsBy::min_by { length($_->pretty) } ($x->alternatives(%opt)))[0]);
}
sub expand {
my ($x, %opt) = @_;
$x->{_expand} //= ((List::UtilsBy::max_by { length($_->pretty) } ($x->alternatives(%opt)))[0]);
}
sub alternatives {
($_[0]);
}
1; # End of Math::Bacovia