package Math::Bacovia::Fraction;
use 5.014;
use warnings;
use Class::Multimethods qw();
use parent qw(Math::Bacovia);
our $VERSION = $Math::Bacovia::VERSION;
sub new {
my ($class, $numerator, $denominator) = @_;
if (defined($numerator)) {
Math::Bacovia::_check_type(\$numerator);
}
else {
$numerator = $Math::Bacovia::ZERO;
}
if (defined($denominator)) {
Math::Bacovia::_check_type(\$denominator);
}
else {
$denominator = $Math::Bacovia::ONE;
}
bless {
num => $numerator,
den => $denominator,
}, $class;
}
sub get {
my ($x) = @_;
($x->{num}, $x->{den});
}
#
## Operations
#
Class::Multimethods::multimethod add => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} * $y->{den} + $y->{num} * $x->{den},
$x->{den} * $y->{den}
);
#>>>
};
Class::Multimethods::multimethod add => (__PACKAGE__, 'Math::Bacovia::Number') => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} + $x->{den} * $y,
$x->{den}
);
#>>>
};
Class::Multimethods::multimethod add => (__PACKAGE__, 'Math::Bacovia::Difference') => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} + $x->{den} * $y,
$x->{den}
);
#>>>
};
Class::Multimethods::multimethod sub => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} * $y->{den} - $y->{num} * $x->{den},
$x->{den} * $y->{den}
);
#>>>
};
Class::Multimethods::multimethod sub => (__PACKAGE__, 'Math::Bacovia::Number') => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} - $x->{den} * $y,
$x->{den}
);
#>>>
};
Class::Multimethods::multimethod sub => (__PACKAGE__, 'Math::Bacovia::Difference') => sub {
my ($x, $y) = @_;
#<<<
__PACKAGE__->new(
$x->{num} - $x->{den} * $y,
$x->{den}
);
#>>>
};
Class::Multimethods::multimethod mul => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num} * $y->{num}, $x->{den} * $y->{den});
};
Class::Multimethods::multimethod mul => (__PACKAGE__, 'Math::Bacovia::Number') => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num} * $y, $x->{den});
};
Class::Multimethods::multimethod mul => (__PACKAGE__, 'Math::Bacovia::Difference') => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num} * $y, $x->{den});
};
Class::Multimethods::multimethod div => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num} * $y->{den}, $x->{den} * $y->{num});
};
Class::Multimethods::multimethod div => (__PACKAGE__, 'Math::Bacovia::Number') => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num}, $x->{den} * $y);
};
Class::Multimethods::multimethod div => (__PACKAGE__, 'Math::Bacovia::Difference') => sub {
my ($x, $y) = @_;
__PACKAGE__->new($x->{num}, $x->{den} * $y);
};
sub inv {
my ($x) = @_;
$x->{_inv} //= __PACKAGE__->new($x->{den}, $x->{num});
}
#
## Equality
#
Class::Multimethods::multimethod eq => (__PACKAGE__, __PACKAGE__) => sub {
my ($x, $y) = @_;
(ref($x->{num}) eq ref($y->{num}))
&& (ref($x->{den}) eq ref($y->{den}))
&& ($x->{num}->eq($y->{num}))
&& ($x->{den}->eq($y->{den}));
};
Class::Multimethods::multimethod eq => (__PACKAGE__, '*') => sub {
!1;
};
#
## Transformations
#
sub numeric {
my ($x) = @_;
$x->{num}->numeric / $x->{den}->numeric;
}
sub pretty {
my ($x) = @_;
$x->{_pretty} //= do {
my $num = $x->{num}->pretty();
my $den = $x->{den}->pretty();
if ($den eq '1') {
$num;
}
else {
"($num/$den)";
}
};
}
sub stringify {
my ($x) = @_;
$x->{_str} //= "Fraction(" . $x->{num}->stringify() . ', ' . $x->{den}->stringify() . ")";
}
#
## Alternatives
#
sub alternatives {
my ($x, %opt) = @_;
$x->{_alt} //= do {
my @a_num = $x->{num}->alternatives(%opt);
my @a_den = $x->{den}->alternatives(%opt);
my @alt;
foreach my $num (@a_num) {
foreach my $den (@a_den) {
# Identity: x/1 = x
if (ref($den) eq 'Math::Bacovia::Number' and $den->{value} == 1) {
push @alt, $num;
}
# Identity: x/x = 1
if (ref($num) eq ref($den) and $num->eq($den)) {
push @alt, $Math::Bacovia::ONE;
}
push @alt, __PACKAGE__->new($num, $den);
# Identity: 1/x = inv(x)
if (ref($num) eq 'Math::Bacovia::Number' and $num->{value} == 1) {
push @alt, $den->inv;
}
# Identity: a^x / b^x = (a/b)^x
#~ if ( ref($num) eq 'Math::Bacovia::Power'
#~ and ref($den) eq 'Math::Bacovia::Power'
#~ and ref($num->{power}) eq ref($den->{power})
#~ and $num->{power}->eq($den->{power})) {
#~ push @alt, 'Math::Bacovia::Power'->new($num->{base} / $den->{base}, $num->{power});
#~ }
# Identity: a^x / a = a^(x-1)
if ( ref($num) eq 'Math::Bacovia::Power'
and ref($num->{base}) eq ref($den)
and $num->{base}->eq($den)) {
push @alt, 'Math::Bacovia::Power'->new($num->{base}, $num->{power} - $Math::Bacovia::ONE);
}
if ($opt{full}) {
push @alt, $den->inv * $num;
push @alt, $num * $den->inv;
push @alt, $num / $den;
# Identity: (a + b) / c = a/c + b/c
if (ref($num) eq 'Math::Bacovia::Sum') {
push @alt, 'Math::Bacovia::Sum'->new(map { $_ / $den } @{$num->{values}});
}
# Identity: (a * b) / c = (a * b * 1/c)
if (ref($num) eq 'Math::Bacovia::Product') {
push @alt, $num * $den->inv;
}
}
elsif ( ref($num) eq 'Math::Bacovia::Number'
and ref($den) eq 'Math::Bacovia::Number') {
push @alt, $num / $den;
}
}
}
[List::UtilsBy::uniq_by { $_->stringify } @alt];
};
@{$x->{_alt}};
}
1;