# -- Daniel S. Lewart Since Sep 1997
#
-require Exporter;
package Math::Complex;
-use 5.005_64;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $Inf);
+
+$VERSION = 1.35;
+
+BEGIN {
+ unless ($^O eq 'unicosmk') {
+ my $e = $!;
+ # We do want an arithmetic overflow, Inf INF inf Infinity:.
+ undef $Inf unless eval <<'EOE' and $Inf =~ /^inf(?:inity)?$/i;
+ local $SIG{FPE} = sub {die};
+ my $t = CORE::exp 30;
+ $Inf = CORE::exp $t;
+EOE
+ if (!defined $Inf) { # Try a different method
+ undef $Inf unless eval <<'EOE' and $Inf =~ /^inf(?:inity)?$/i;
+ local $SIG{FPE} = sub {die};
+ my $t = 1;
+ $Inf = $t + "1e99999999999999999999999999999999";
+EOE
+ }
+ $! = $e; # Clear ERANGE.
+ }
+ $Inf = "Inf" if !defined $Inf || !($Inf > 0); # Desperation.
+}
+
use strict;
-our($VERSION, @ISA, @EXPORT, %EXPORT_TAGS);
+my $i;
+my %LOGN;
-my ( $i, $ip2, %logn );
+# Regular expression for floating point numbers.
+# These days we could use Scalar::Util::lln(), I guess.
+my $gre = qr'\s*([\+\-]?(?:(?:(?:\d+(?:_\d+)*(?:\.\d*(?:_\d+)*)?|\.\d+(?:_\d+)*)(?:[eE][\+\-]?\d+(?:_\d+)*)?))|inf)'i;
-$VERSION = sprintf("%s", q$Id: Complex.pm,v 1.26 1998/11/01 00:00:00 dsl Exp $ =~ /(\d+\.\d+)/);
+require Exporter;
@ISA = qw(Exporter);
sqrt log ln
log10 logn cbrt root
cplx cplxe
+ atan2
),
@trig);
+@EXPORT_OK = qw(decplx);
+
%EXPORT_TAGS = (
'trig' => [@trig],
);
'*' => \&multiply,
'/' => \÷,
'**' => \&power,
+ '==' => \&numeq,
'<=>' => \&spaceship,
'neg' => \&negate,
'~' => \&conjugate,
# Package "privates"
#
-my $package = 'Math::Complex'; # Package name
my %DISPLAY_FORMAT = ('style' => 'cartesian',
'polar_pretty_print' => 1);
my $eps = 1e-14; # Epsilon
# Die on bad *make() arguments.
sub _cannot_make {
- die "@{[(caller(1))[3]]}: Cannot take $_[0] of $_[1].\n";
+ die "@{[(caller(1))[3]]}: Cannot take $_[0] of '$_[1]'.\n";
+}
+
+sub _make {
+ my $arg = shift;
+ my ($p, $q);
+
+ if ($arg =~ /^$gre$/) {
+ ($p, $q) = ($1, 0);
+ } elsif ($arg =~ /^(?:$gre)?$gre\s*i\s*$/) {
+ ($p, $q) = ($1 || 0, $2);
+ } elsif ($arg =~ /^\s*\(\s*$gre\s*(?:,\s*$gre\s*)?\)\s*$/) {
+ ($p, $q) = ($1, $2 || 0);
+ }
+
+ if (defined $p) {
+ $p =~ s/^\+//;
+ $p =~ s/^(-?)inf$/"${1}9**9**9"/e;
+ $q =~ s/^\+//;
+ $q =~ s/^(-?)inf$/"${1}9**9**9"/e;
+ }
+
+ return ($p, $q);
+}
+
+sub _emake {
+ my $arg = shift;
+ my ($p, $q);
+
+ if ($arg =~ /^\s*\[\s*$gre\s*(?:,\s*$gre\s*)?\]\s*$/) {
+ ($p, $q) = ($1, $2 || 0);
+ } elsif ($arg =~ m!^\s*\[\s*$gre\s*(?:,\s*([-+]?\d*\s*)?pi(?:/\s*(\d+))?\s*)?\]\s*$!) {
+ ($p, $q) = ($1, ($2 eq '-' ? -1 : ($2 || 1)) * pi() / ($3 || 1));
+ } elsif ($arg =~ /^\s*\[\s*$gre\s*\]\s*$/) {
+ ($p, $q) = ($1, 0);
+ } elsif ($arg =~ /^\s*$gre\s*$/) {
+ ($p, $q) = ($1, 0);
+ }
+
+ if (defined $p) {
+ $p =~ s/^\+//;
+ $q =~ s/^\+//;
+ $p =~ s/^(-?)inf$/"${1}9**9**9"/e;
+ $q =~ s/^(-?)inf$/"${1}9**9**9"/e;
+ }
+
+ return ($p, $q);
}
#
# Create a new complex number (cartesian form)
#
sub make {
- my $self = bless {}, shift;
- my ($re, $im) = @_;
- my $rre = ref $re;
- if ( $rre ) {
- if ( $rre eq ref $self ) {
- $re = Re($re);
- } else {
- _cannot_make("real part", $rre);
- }
- }
- my $rim = ref $im;
- if ( $rim ) {
- if ( $rim eq ref $self ) {
- $im = Im($im);
- } else {
- _cannot_make("imaginary part", $rim);
- }
- }
- $self->{'cartesian'} = [ $re, $im ];
- $self->{c_dirty} = 0;
- $self->{p_dirty} = 1;
- $self->display_format('cartesian');
- return $self;
+ my $self = bless {}, shift;
+ my ($re, $im);
+ if (@_ == 0) {
+ ($re, $im) = (0, 0);
+ } elsif (@_ == 1) {
+ return (ref $self)->emake($_[0])
+ if ($_[0] =~ /^\s*\[/);
+ ($re, $im) = _make($_[0]);
+ } elsif (@_ == 2) {
+ ($re, $im) = @_;
+ }
+ if (defined $re) {
+ _cannot_make("real part", $re) unless $re =~ /^$gre$/;
+ }
+ $im ||= 0;
+ _cannot_make("imaginary part", $im) unless $im =~ /^$gre$/;
+ $self->set_cartesian([$re, $im ]);
+ $self->display_format('cartesian');
+
+ return $self;
}
#
# Create a new complex number (exponential form)
#
sub emake {
- my $self = bless {}, shift;
- my ($rho, $theta) = @_;
- my $rrh = ref $rho;
- if ( $rrh ) {
- if ( $rrh eq ref $self ) {
- $rho = rho($rho);
- } else {
- _cannot_make("rho", $rrh);
- }
- }
- my $rth = ref $theta;
- if ( $rth ) {
- if ( $rth eq ref $self ) {
- $theta = theta($theta);
- } else {
- _cannot_make("theta", $rth);
- }
- }
+ my $self = bless {}, shift;
+ my ($rho, $theta);
+ if (@_ == 0) {
+ ($rho, $theta) = (0, 0);
+ } elsif (@_ == 1) {
+ return (ref $self)->make($_[0])
+ if ($_[0] =~ /^\s*\(/ || $_[0] =~ /i\s*$/);
+ ($rho, $theta) = _emake($_[0]);
+ } elsif (@_ == 2) {
+ ($rho, $theta) = @_;
+ }
+ if (defined $rho && defined $theta) {
if ($rho < 0) {
$rho = -$rho;
$theta = ($theta <= 0) ? $theta + pi() : $theta - pi();
}
- $self->{'polar'} = [$rho, $theta];
- $self->{p_dirty} = 0;
- $self->{c_dirty} = 1;
- $self->display_format('polar');
- return $self;
+ }
+ if (defined $rho) {
+ _cannot_make("rho", $rho) unless $rho =~ /^$gre$/;
+ }
+ $theta ||= 0;
+ _cannot_make("theta", $theta) unless $theta =~ /^$gre$/;
+ $self->set_polar([$rho, $theta]);
+ $self->display_format('polar');
+
+ return $self;
}
sub new { &make } # For backward compatibility only.
# This avoids the burden of writing Math::Complex->make(re, im).
#
sub cplx {
- my ($re, $im) = @_;
- return __PACKAGE__->make($re, defined $im ? $im : 0);
+ return __PACKAGE__->make(@_);
}
#
# This avoids the burden of writing Math::Complex->emake(rho, theta).
#
sub cplxe {
- my ($rho, $theta) = @_;
- return __PACKAGE__->emake($rho, defined $theta ? $theta : 0);
+ return __PACKAGE__->emake(@_);
}
#
}
#
+# ip2
+#
+# Half of i.
+#
+sub ip2 () { i / 2 }
+
+#
# Attribute access/set routines
#
sub polar {$_[0]->{p_dirty} ?
$_[0]->update_polar : $_[0]->{'polar'}}
-sub set_cartesian { $_[0]->{p_dirty}++; $_[0]->{'cartesian'} = $_[1] }
-sub set_polar { $_[0]->{c_dirty}++; $_[0]->{'polar'} = $_[1] }
+sub set_cartesian { $_[0]->{p_dirty}++; $_[0]->{c_dirty} = 0;
+ $_[0]->{'cartesian'} = $_[1] }
+sub set_polar { $_[0]->{c_dirty}++; $_[0]->{p_dirty} = 0;
+ $_[0]->{'polar'} = $_[1] }
#
# ->update_cartesian
my ($x, $y) = @{$self->{'cartesian'}};
$self->{p_dirty} = 0;
return $self->{'polar'} = [0, 0] if $x == 0 && $y == 0;
- return $self->{'polar'} = [CORE::sqrt($x*$x + $y*$y), CORE::atan2($y, $x)];
+ return $self->{'polar'} = [CORE::sqrt($x*$x + $y*$y),
+ CORE::atan2($y, $x)];
}
#
if (defined $_[1]) {
$mess .= "(Because in the definition of $_[0], the divisor ";
- $mess .= "$_[1] " unless ($_[1] eq '0');
+ $mess .= "$_[1] " unless ("$_[1]" eq '0');
$mess .= "is 0)\n";
}
return 1 if $z2 == 0 || $z1 == 1;
return 0 if $z1 == 0 && Re($z2) > 0;
}
- my $w = $inverted ? CORE::exp($z1 * CORE::log($z2))
- : CORE::exp($z2 * CORE::log($z1));
+ my $w = $inverted ? &exp($z1 * &log($z2))
+ : &exp($z2 * &log($z1));
# If both arguments cartesian, return cartesian, else polar.
return $z1->{c_dirty} == 0 &&
(not ref $z2 or $z2->{c_dirty} == 0) ?
}
#
+# (numeq)
+#
+# Computes z1 == z2.
+#
+# (Required in addition to spaceship() because of NaNs.)
+sub numeq {
+ my ($z1, $z2, $inverted) = @_;
+ my ($re1, $im1) = ref $z1 ? @{$z1->cartesian} : ($z1, 0);
+ my ($re2, $im2) = ref $z2 ? @{$z2->cartesian} : ($z2, 0);
+ return $re1 == $re2 && $im1 == $im2 ? 1 : 0;
+}
+
+#
# (negate)
#
# Computes -z.
#
sub abs {
my ($z, $rho) = @_;
- return $z unless ref $z;
+ unless (ref $z) {
+ if (@_ == 2) {
+ $_[0] = $_[1];
+ } else {
+ return CORE::abs($z);
+ }
+ }
if (defined $rho) {
$z->{'polar'} = [ $rho, ${$z->polar}[1] ];
$z->{p_dirty} = 0;
sub sqrt {
my ($z) = @_;
my ($re, $im) = ref $z ? @{$z->cartesian} : ($z, 0);
- return $re < 0 ? cplx(0, CORE::sqrt(-$re)) : CORE::sqrt($re) if $im == 0;
+ return $re < 0 ? cplx(0, CORE::sqrt(-$re)) : CORE::sqrt($re)
+ if $im == 0;
my ($r, $t) = @{$z->polar};
return (ref $z)->emake(CORE::sqrt($r), $t/2);
}
#
sub cbrt {
my ($z) = @_;
- return $z < 0 ? -CORE::exp(CORE::log(-$z)/3) : ($z > 0 ? CORE::exp(CORE::log($z)/3): 0)
+ return $z < 0 ?
+ -CORE::exp(CORE::log(-$z)/3) :
+ ($z > 0 ? CORE::exp(CORE::log($z)/3): 0)
unless ref $z;
my ($r, $t) = @{$z->polar};
+ return 0 if $r == 0;
return (ref $z)->emake(CORE::exp(CORE::log($r)/3), $t/3);
}
# Die on bad root.
#
sub _rootbad {
- my $mess = "Root $_[0] not defined, root must be positive integer.\n";
+ my $mess = "Root '$_[0]' illegal, root rank must be positive integer.\n";
my @up = caller(1);
# z^(1/n) = r^(1/n) (cos ((t+2 k pi)/n) + i sin ((t+2 k pi)/n))
#
sub root {
- my ($z, $n) = @_;
+ my ($z, $n, $k) = @_;
_rootbad($n) if ($n < 1 or int($n) != $n);
- my ($r, $t) = ref $z ? @{$z->polar} : (CORE::abs($z), $z >= 0 ? 0 : pi);
- my @root;
- my $k;
+ my ($r, $t) = ref $z ?
+ @{$z->polar} : (CORE::abs($z), $z >= 0 ? 0 : pi);
my $theta_inc = pit2 / $n;
my $rho = $r ** (1/$n);
- my $theta;
my $cartesian = ref $z && $z->{c_dirty} == 0;
- for ($k = 0, $theta = $t / $n; $k < $n; $k++, $theta += $theta_inc) {
- my $w = cplxe($rho, $theta);
- # Yes, $cartesian is loop invariant.
- push @root, $cartesian ? cplx(@{$w->cartesian}) : $w;
+ if (@_ == 2) {
+ my @root;
+ for (my $i = 0, my $theta = $t / $n;
+ $i < $n;
+ $i++, $theta += $theta_inc) {
+ my $w = cplxe($rho, $theta);
+ # Yes, $cartesian is loop invariant.
+ push @root, $cartesian ? cplx(@{$w->cartesian}) : $w;
+ }
+ return @root;
+ } elsif (@_ == 3) {
+ my $w = cplxe($rho, $t / $n + $k * $theta_inc);
+ return $cartesian ? cplx(@{$w->cartesian}) : $w;
}
- return @root;
}
#
#
sub Im {
my ($z, $Im) = @_;
- return $z unless ref $z;
+ return 0 unless ref $z;
if (defined $Im) {
$z->{'cartesian'} = [ ${$z->cartesian}[0], $Im ];
$z->{c_dirty} = 0;
sub logn {
my ($z, $n) = @_;
$z = cplx($z, 0) unless ref $z;
- my $logn = $logn{$n};
- $logn = $logn{$n} = CORE::log($n) unless defined $logn; # Cache log(n)
- return CORE::log($z) / $logn;
+ my $logn = $LOGN{$n};
+ $logn = $LOGN{$n} = CORE::log($n) unless defined $logn; # Cache log(n)
+ return &log($z) / $logn;
}
#
#
sub cos {
my ($z) = @_;
+ return CORE::cos($z) unless ref $z;
my ($x, $y) = @{$z->cartesian};
my $ey = CORE::exp($y);
- my $ey_1 = 1 / $ey;
- return (ref $z)->make(CORE::cos($x) * ($ey + $ey_1)/2,
- CORE::sin($x) * ($ey_1 - $ey)/2);
+ my $sx = CORE::sin($x);
+ my $cx = CORE::cos($x);
+ my $ey_1 = $ey ? 1 / $ey : $Inf;
+ return (ref $z)->make($cx * ($ey + $ey_1)/2,
+ $sx * ($ey_1 - $ey)/2);
}
#
#
sub sin {
my ($z) = @_;
+ return CORE::sin($z) unless ref $z;
my ($x, $y) = @{$z->cartesian};
my $ey = CORE::exp($y);
- my $ey_1 = 1 / $ey;
- return (ref $z)->make(CORE::sin($x) * ($ey + $ey_1)/2,
- CORE::cos($x) * ($ey - $ey_1)/2);
+ my $sx = CORE::sin($x);
+ my $cx = CORE::cos($x);
+ my $ey_1 = $ey ? 1 / $ey : $Inf;
+ return (ref $z)->make($sx * ($ey + $ey_1)/2,
+ $cx * ($ey - $ey_1)/2);
}
#
#
sub tan {
my ($z) = @_;
- my $cz = CORE::cos($z);
- _divbyzero "tan($z)", "cos($z)" if (CORE::abs($cz) < $eps);
- return CORE::sin($z) / $cz;
+ my $cz = &cos($z);
+ _divbyzero "tan($z)", "cos($z)" if $cz == 0;
+ return &sin($z) / $cz;
}
#
#
sub sec {
my ($z) = @_;
- my $cz = CORE::cos($z);
+ my $cz = &cos($z);
_divbyzero "sec($z)", "cos($z)" if ($cz == 0);
return 1 / $cz;
}
#
sub csc {
my ($z) = @_;
- my $sz = CORE::sin($z);
+ my $sz = &sin($z);
_divbyzero "csc($z)", "sin($z)" if ($sz == 0);
return 1 / $sz;
}
#
sub cot {
my ($z) = @_;
- my $sz = CORE::sin($z);
+ my $sz = &sin($z);
_divbyzero "cot($z)", "sin($z)" if ($sz == 0);
- return CORE::cos($z) / $sz;
+ return &cos($z) / $sz;
}
#
#
sub acos {
my $z = $_[0];
- return CORE::atan2(CORE::sqrt(1-$z*$z), $z) if (! ref $z) && CORE::abs($z) <= 1;
- my ($x, $y) = ref $z ? @{$z->cartesian} : ($z, 0);
+ return CORE::atan2(CORE::sqrt(1-$z*$z), $z)
+ if (! ref $z) && CORE::abs($z) <= 1;
+ $z = cplx($z, 0) unless ref $z;
+ my ($x, $y) = @{$z->cartesian};
+ return 0 if $x == 1 && $y == 0;
my $t1 = CORE::sqrt(($x+1)*($x+1) + $y*$y);
my $t2 = CORE::sqrt(($x-1)*($x-1) + $y*$y);
my $alpha = ($t1 + $t2)/2;
my $u = CORE::atan2(CORE::sqrt(1-$beta*$beta), $beta);
my $v = CORE::log($alpha + CORE::sqrt($alpha*$alpha-1));
$v = -$v if $y > 0 || ($y == 0 && $x < -1);
- return __PACKAGE__->make($u, $v);
+ return (ref $z)->make($u, $v);
}
#
#
sub asin {
my $z = $_[0];
- return CORE::atan2($z, CORE::sqrt(1-$z*$z)) if (! ref $z) && CORE::abs($z) <= 1;
- my ($x, $y) = ref $z ? @{$z->cartesian} : ($z, 0);
+ return CORE::atan2($z, CORE::sqrt(1-$z*$z))
+ if (! ref $z) && CORE::abs($z) <= 1;
+ $z = cplx($z, 0) unless ref $z;
+ my ($x, $y) = @{$z->cartesian};
+ return 0 if $x == 0 && $y == 0;
my $t1 = CORE::sqrt(($x+1)*($x+1) + $y*$y);
my $t2 = CORE::sqrt(($x-1)*($x-1) + $y*$y);
my $alpha = ($t1 + $t2)/2;
my $u = CORE::atan2($beta, CORE::sqrt(1-$beta*$beta));
my $v = -CORE::log($alpha + CORE::sqrt($alpha*$alpha-1));
$v = -$v if $y > 0 || ($y == 0 && $x < -1);
- return __PACKAGE__->make($u, $v);
+ return (ref $z)->make($u, $v);
}
#
sub atan {
my ($z) = @_;
return CORE::atan2($z, 1) unless ref $z;
+ my ($x, $y) = ref $z ? @{$z->cartesian} : ($z, 0);
+ return 0 if $x == 0 && $y == 0;
_divbyzero "atan(i)" if ( $z == i);
- _divbyzero "atan(-i)" if (-$z == i);
- my $log = CORE::log((i + $z) / (i - $z));
- $ip2 = 0.5 * i unless defined $ip2;
- return $ip2 * $log;
+ _logofzero "atan(-i)" if (-$z == i); # -i is a bad file test...
+ my $log = &log((i + $z) / (i - $z));
+ return ip2 * $log;
}
#
#
sub acot {
my ($z) = @_;
- _divbyzero "acot(0)" if (CORE::abs($z) < $eps);
- return ($z >= 0) ? CORE::atan2(1, $z) : CORE::atan2(-1, -$z) unless ref $z;
- _divbyzero "acot(i)" if (CORE::abs($z - i) < $eps);
- _logofzero "acot(-i)" if (CORE::abs($z + i) < $eps);
+ _divbyzero "acot(0)" if $z == 0;
+ return ($z >= 0) ? CORE::atan2(1, $z) : CORE::atan2(-1, -$z)
+ unless ref $z;
+ _divbyzero "acot(i)" if ($z - i == 0);
+ _logofzero "acot(-i)" if ($z + i == 0);
return atan(1 / $z);
}
my $ex;
unless (ref $z) {
$ex = CORE::exp($z);
- return ($ex + 1/$ex)/2;
+ return $ex ? ($ex + 1/$ex)/2 : $Inf;
}
my ($x, $y) = @{$z->cartesian};
$ex = CORE::exp($x);
- my $ex_1 = 1 / $ex;
+ my $ex_1 = $ex ? 1 / $ex : $Inf;
return (ref $z)->make(CORE::cos($y) * ($ex + $ex_1)/2,
CORE::sin($y) * ($ex - $ex_1)/2);
}
my ($z) = @_;
my $ex;
unless (ref $z) {
+ return 0 if $z == 0;
$ex = CORE::exp($z);
- return ($ex - 1/$ex)/2;
+ return $ex ? ($ex - 1/$ex)/2 : "-$Inf";
}
my ($x, $y) = @{$z->cartesian};
+ my $cy = CORE::cos($y);
+ my $sy = CORE::sin($y);
$ex = CORE::exp($x);
- my $ex_1 = 1 / $ex;
+ my $ex_1 = $ex ? 1 / $ex : $Inf;
return (ref $z)->make(CORE::cos($y) * ($ex - $ex_1)/2,
CORE::sin($y) * ($ex + $ex_1)/2);
}
sub coth {
my ($z) = @_;
my $sz = sinh($z);
- _divbyzero "coth($z)", "sinh($z)" if ($sz == 0);
+ _divbyzero "coth($z)", "sinh($z)" if $sz == 0;
return cosh($z) / $sz;
}
sub acosh {
my ($z) = @_;
unless (ref $z) {
- return CORE::log($z + CORE::sqrt($z*$z-1)) if $z >= 1;
$z = cplx($z, 0);
}
my ($re, $im) = @{$z->cartesian};
if ($im == 0) {
- return cplx(CORE::log($re + CORE::sqrt($re*$re - 1)), 0) if $re >= 1;
- return cplx(0, CORE::atan2(CORE::sqrt(1-$re*$re), $re)) if CORE::abs($re) <= 1;
+ return CORE::log($re + CORE::sqrt($re*$re - 1))
+ if $re >= 1;
+ return cplx(0, CORE::atan2(CORE::sqrt(1 - $re*$re), $re))
+ if CORE::abs($re) < 1;
}
- return CORE::log($z + CORE::sqrt($z*$z - 1));
+ my $t = &sqrt($z * $z - 1) + $z;
+ # Try Taylor if looking bad (this usually means that
+ # $z was large negative, therefore the sqrt is really
+ # close to abs(z), summing that with z...)
+ $t = 1/(2 * $z) - 1/(8 * $z**3) + 1/(16 * $z**5) - 5/(128 * $z**7)
+ if $t == 0;
+ my $u = &log($t);
+ $u->Im(-$u->Im) if $re < 0 && $im == 0;
+ return $re < 0 ? -$u : $u;
}
#
# asinh
#
-# Computes the arc hyperbolic sine asinh(z) = log(z + sqrt(z*z-1))
+# Computes the arc hyperbolic sine asinh(z) = log(z + sqrt(z*z+1))
#
sub asinh {
my ($z) = @_;
- return CORE::log($z + CORE::sqrt($z*$z + 1));
+ unless (ref $z) {
+ my $t = $z + CORE::sqrt($z*$z + 1);
+ return CORE::log($t) if $t;
+ }
+ my $t = &sqrt($z * $z + 1) + $z;
+ # Try Taylor if looking bad (this usually means that
+ # $z was large negative, therefore the sqrt is really
+ # close to abs(z), summing that with z...)
+ $t = 1/(2 * $z) - 1/(8 * $z**3) + 1/(16 * $z**5) - 5/(128 * $z**7)
+ if $t == 0;
+ return &log($t);
}
#
return CORE::log((1 + $z)/(1 - $z))/2 if CORE::abs($z) < 1;
$z = cplx($z, 0);
}
- _divbyzero 'atanh(1)', "1 - $z" if ($z == 1);
- _logofzero 'atanh(-1)' if ($z == -1);
- return 0.5 * CORE::log((1 + $z) / (1 - $z));
+ _divbyzero 'atanh(1)', "1 - $z" if (1 - $z == 0);
+ _logofzero 'atanh(-1)' if (1 + $z == 0);
+ return 0.5 * &log((1 + $z) / (1 - $z));
}
#
#
sub asech {
my ($z) = @_;
- _divbyzero 'asech(0)', $z if ($z == 0);
+ _divbyzero 'asech(0)', "$z" if ($z == 0);
return acosh(1 / $z);
}
#
sub acoth {
my ($z) = @_;
- _divbyzero 'acoth(0)' if (CORE::abs($z) < $eps);
+ _divbyzero 'acoth(0)' if ($z == 0);
unless (ref $z) {
return CORE::log(($z + 1)/($z - 1))/2 if CORE::abs($z) > 1;
$z = cplx($z, 0);
}
- _divbyzero 'acoth(1)', "$z - 1" if (CORE::abs($z - 1) < $eps);
- _logofzero 'acoth(-1)', "1 / $z" if (CORE::abs($z + 1) < $eps);
- return CORE::log((1 + $z) / ($z - 1)) / 2;
+ _divbyzero 'acoth(1)', "$z - 1" if ($z - 1 == 0);
+ _logofzero 'acoth(-1)', "1 + $z" if (1 + $z == 0);
+ return &log((1 + $z) / ($z - 1)) / 2;
}
#
#
# (atan2)
#
-# Compute atan(z1/z2).
+# Compute atan(z1/z2), minding the right quadrant.
#
sub atan2 {
my ($z1, $z2, $inverted) = @_;
my ($re1, $im1, $re2, $im2);
if ($inverted) {
($re1, $im1) = ref $z2 ? @{$z2->cartesian} : ($z2, 0);
- ($re2, $im2) = @{$z1->cartesian};
+ ($re2, $im2) = ref $z1 ? @{$z1->cartesian} : ($z1, 0);
} else {
- ($re1, $im1) = @{$z1->cartesian};
+ ($re1, $im1) = ref $z1 ? @{$z1->cartesian} : ($z1, 0);
($re2, $im2) = ref $z2 ? @{$z2->cartesian} : ($z2, 0);
}
- if ($im2 == 0) {
- return cplx(CORE::atan2($re1, $re2), 0) if $im1 == 0;
- return cplx(($im1<=>0) * pip2, 0) if $re2 == 0;
+ if ($im1 || $im2) {
+ # In MATLAB the imaginary parts are ignored.
+ # warn "atan2: Imaginary parts ignored";
+ # http://documents.wolfram.com/mathematica/functions/ArcTan
+ # NOTE: Mathematica ArcTan[x,y] while atan2(y,x)
+ my $s = $z1 * $z1 + $z2 * $z2;
+ _divbyzero("atan2") if $s == 0;
+ my $i = &i;
+ my $r = $z2 + $z1 * $i;
+ return -$i * &log($r / &sqrt( $s ));
}
- my $w = atan($z1/$z2);
- my ($u, $v) = ref $w ? @{$w->cartesian} : ($w, 0);
- $u += pi if $re2 < 0;
- $u -= pit2 if $u > pi;
- return cplx($u, $v);
+ return CORE::atan2($re1, $re2);
}
#
my %obj = %{$self->{display_format}};
@display_format{keys %obj} = values %obj;
}
- if (@_ == 1) {
- $display_format{style} = shift;
- } else {
- my %new = @_;
- @display_format{keys %new} = values %new;
- }
- } else { # Called as a class method
- if (@_ = 1) {
- $display_format{style} = $self;
- } else {
- my %new = @_;
- @display_format{keys %new} = values %new;
- }
- undef $self;
+ }
+ if (@_ == 1) {
+ $display_format{style} = shift;
+ } else {
+ my %new = @_;
+ @display_format{keys %new} = values %new;
}
- if (defined $self) {
+ if (ref $self) { # Called as an object method
$self->{display_format} = { %display_format };
return
wantarray ?
$self->{display_format}->{style};
}
+ # Called as a class method
%DISPLAY_FORMAT = %display_format;
return
wantarray ?
my ($x, $y) = @{$z->cartesian};
my ($re, $im);
- $x = int($x + ($x < 0 ? -1 : 1) * $eps)
- if int(CORE::abs($x)) != int(CORE::abs($x) + $eps);
- $y = int($y + ($y < 0 ? -1 : 1) * $eps)
- if int(CORE::abs($y)) != int(CORE::abs($y) + $eps);
-
- $re = "$x" if CORE::abs($x) >= $eps;
-
my %format = $z->display_format;
my $format = $format{format};
- if ($y == 1) { $im = 'i' }
- elsif ($y == -1) { $im = '-i' }
- elsif (CORE::abs($y) >= $eps) {
- $im = (defined $format ? sprintf($format, $y) : $y) . "i";
+ if ($x) {
+ if ($x =~ /^NaN[QS]?$/i) {
+ $re = $x;
+ } else {
+ if ($x =~ /^-?$Inf$/oi) {
+ $re = $x;
+ } else {
+ $re = defined $format ? sprintf($format, $x) : $x;
+ }
+ }
+ } else {
+ undef $re;
+ }
+
+ if ($y) {
+ if ($y =~ /^(NaN[QS]?)$/i) {
+ $im = $y;
+ } else {
+ if ($y =~ /^-?$Inf$/oi) {
+ $im = $y;
+ } else {
+ $im =
+ defined $format ?
+ sprintf($format, $y) :
+ ($y == 1 ? "" : ($y == -1 ? "-" : $y));
+ }
+ }
+ $im .= "i";
+ } else {
+ undef $im;
}
- my $str = '';
- $str = defined $format ? sprintf($format, $re) : $re
- if defined $re;
+ my $str = $re;
+
if (defined $im) {
if ($y < 0) {
$str .= $im;
- } elsif ($y > 0) {
+ } elsif ($y > 0 || $im =~ /^NaN[QS]?i$/i) {
$str .= "+" if defined $re;
$str .= $im;
}
+ } elsif (!defined $re) {
+ $str = "0";
}
return $str;
}
-# Helper for stringify_polar, a Greatest Common Divisor with a memory.
-
-sub _gcd {
- my ($a, $b) = @_;
-
- use integer;
-
- # Loops forever if given negative inputs.
-
- if ($b and $a > $b) { return gcd($a % $b, $b) }
- elsif ($a and $b > $a) { return gcd($b % $a, $a) }
- else { return $a ? $a : $b }
-}
-
-my %gcd;
-
-sub gcd {
- my ($a, $b) = @_;
-
- my $id = "$a $b";
-
- unless (exists $gcd{$id}) {
- $gcd{$id} = _gcd($a, $b);
- $gcd{"$b $a"} = $gcd{$id};
- }
-
- return $gcd{$id};
-}
-
#
# ->stringify_polar
#
my ($r, $t) = @{$z->polar};
my $theta;
- return '[0,0]' if $r <= $eps;
-
my %format = $z->display_format;
+ my $format = $format{format};
- my $nt = $t / pit2;
- $nt = ($nt - int($nt)) * pit2;
- $nt += pit2 if $nt < 0; # Range [0, 2pi]
-
- if (CORE::abs($nt) <= $eps) { $theta = 0 }
- elsif (CORE::abs(pi-$nt) <= $eps) { $theta = 'pi' }
-
- if (defined $theta) {
- $r = int($r + ($r < 0 ? -1 : 1) * $eps)
- if int(CORE::abs($r)) != int(CORE::abs($r) + $eps);
- $theta = int($theta + ($theta < 0 ? -1 : 1) * $eps)
- if ($theta ne 'pi' and
- int(CORE::abs($theta)) != int(CORE::abs($theta) + $eps));
- return "\[$r,$theta\]";
+ if ($t =~ /^NaN[QS]?$/i || $t =~ /^-?$Inf$/oi) {
+ $theta = $t;
+ } elsif ($t == pi) {
+ $theta = "pi";
+ } elsif ($r == 0 || $t == 0) {
+ $theta = defined $format ? sprintf($format, $t) : $t;
}
+ return "[$r,$theta]" if defined $theta;
+
#
- # Okay, number is not a real. Try to identify pi/n and friends...
+ # Try to identify pi/n and friends.
#
- $nt -= pit2 if $nt > pi;
-
- if ($format{polar_pretty_print} && CORE::abs($nt) >= deg1) {
- my ($n, $k, $kpi);
-
- for ($k = 1, $kpi = pi; $k < 10; $k++, $kpi += pi) {
- $n = int($kpi / $nt + ($nt > 0 ? 1 : -1) * 0.5);
- if (CORE::abs($kpi/$n - $nt) <= $eps) {
- $n = CORE::abs($n);
- my $gcd = gcd($k, $n);
- if ($gcd > 1) {
- $k /= $gcd;
- $n /= $gcd;
- }
- next if $n > 360;
- $theta = ($nt < 0 ? '-':'').
- ($k == 1 ? 'pi':"${k}pi");
- $theta .= '/'.$n if $n > 1;
+ $t -= int(CORE::abs($t) / pit2) * pit2;
+
+ if ($format{polar_pretty_print} && $t) {
+ my ($a, $b);
+ for $a (2..9) {
+ $b = $t * $a / pi;
+ if ($b =~ /^-?\d+$/) {
+ $b = $b < 0 ? "-" : "" if CORE::abs($b) == 1;
+ $theta = "${b}pi/$a";
last;
}
}
}
- $theta = $nt unless defined $theta;
-
- $r = int($r + ($r < 0 ? -1 : 1) * $eps)
- if int(CORE::abs($r)) != int(CORE::abs($r) + $eps);
- $theta = int($theta + ($theta < 0 ? -1 : 1) * $eps)
- if ($theta !~ m(^-?\d*pi/\d+$) and
- int(CORE::abs($theta)) != int(CORE::abs($theta) + $eps));
-
- my $format = $format{format};
if (defined $format) {
$r = sprintf($format, $r);
- $theta = sprintf($format, $theta);
+ $theta = sprintf($format, $theta) unless defined $theta;
+ } else {
+ $theta = $t unless defined $theta;
}
- return "\[$r,$theta\]";
+ return "[$r,$theta]";
}
1;
__END__
+=pod
+
=head1 NAME
Math::Complex - complex numbers and associated mathematical functions
A I<new> operation possible on a complex number that is
the identity for real numbers is called the I<conjugate>, and is noted
-with an horizontal bar above the number, or C<~z> here.
+with a horizontal bar above the number, or C<~z> here.
z = a + bi
~z = a - bi
log(z) = log(r1) + i*t
sin(z) = 1/2i (exp(i * z1) - exp(-i * z))
cos(z) = 1/2 (exp(i * z1) + exp(-i * z))
- atan2(z1, z2) = atan(z1/z2)
+ atan2(y, x) = atan(y / x) # Minding the right quadrant, note the order.
+
+The definition used for complex arguments of atan2() is
+
+ -i log((x + iy)/sqrt(x*x+y*y))
The following extra operations are supported on both real and complex
numbers:
I<coth>, I<acosech>, I<acotanh>, have aliases I<rho>, I<theta>, I<ln>,
I<cosec>, I<cotan>, I<acosec>, I<acotan>, I<cosech>, I<cotanh>,
I<acosech>, I<acotanh>, respectively. C<Re>, C<Im>, C<arg>, C<abs>,
-C<rho>, and C<theta> can be used also also mutators. The C<cbrt>
+C<rho>, and C<theta> can be used also as mutators. The C<cbrt>
returns only one of the solutions: if you want all three, use the
C<root> function.
(root(z, n))[k] = r**(1/n) * exp(i * (t + 2*k*pi)/n)
+You can return the I<k>th root directly by C<root(z, n, k)>,
+indexing starting from I<zero> and ending at I<n - 1>.
+
The I<spaceship> comparison operator, E<lt>=E<gt>, is also defined. In
order to ensure its restriction to real numbers is conform to what you
would expect, the comparison is run on the real part of the complex
modulus must be non-negative (it represents the distance to the origin
in the complex plane).
-It is also possible to have a complex number as either argument of
-either the C<make> or C<emake>: the appropriate component of
+It is also possible to have a complex number as either argument of the
+C<make>, C<emake>, C<cplx>, and C<cplxe>: the appropriate component of
the argument will be used.
$z1 = cplx(-2, 1);
$z2 = cplx($z1, 4);
-=head1 STRINGIFICATION
+The C<new>, C<make>, C<emake>, C<cplx>, and C<cplxe> will also
+understand a single (string) argument of the forms
+
+ 2-3i
+ -3i
+ [2,3]
+ [2,-3pi/4]
+ [2]
+
+in which case the appropriate cartesian and exponential components
+will be parsed from the string and used to create new complex numbers.
+The imaginary component and the theta, respectively, will default to zero.
+
+The C<new>, C<make>, C<emake>, C<cplx>, and C<cplxe> will also
+understand the case of no arguments: this means plain zero or (0, 0).
+
+=head1 DISPLAYING
When printed, a complex number is usually shown under its cartesian
style I<a+bi>, but there are legitimate cases where the polar style
-I<[r,t]> is more appropriate.
-
-In the polar style Math::Complex will try to recognize certain common
-numbers such as multiples or small rationals of pi (2pi, pi/2) and
-prettyprint those numbers.
+I<[r,t]> is more appropriate. The process of converting the complex
+number into a string that can be displayed is known as I<stringification>.
By calling the class method C<Math::Complex::display_format> and
supplying either C<"polar"> or C<"cartesian"> as an argument, you
-override the default display format, which is C<"cartesian">. Not
+override the default display style, which is C<"cartesian">. Not
supplying any argument returns the current settings.
This default can be overridden on a per-number basis by calling the
C<display_format> method instead. As before, not supplying any argument
-returns the current display format for this number. Otherwise whatever you
-specify will be the new display format for I<this> particular number.
+returns the current display style for this number. Otherwise whatever you
+specify will be the new display style for I<this> particular number.
For instance:
$j->display_format('cartesian');
print "j = $j\n"; # Prints "j = -0.5+0.866025403784439i"
-The polar format attempts to emphasize arguments like I<k*pi/n>
-(where I<n> is a positive integer and I<k> an integer within [-9,+9]).
+The polar style attempts to emphasize arguments like I<k*pi/n>
+(where I<n> is a positive integer and I<k> an integer within [-9, +9]),
+this is called I<polar pretty-printing>.
+
+For the reverse of stringifying, see the C<make> and C<emake>.
=head2 CHANGED IN PERL 5.6
a parameter hash instead of just a one parameter.
The old display format style, which can have values C<"cartesian"> or
-C<"polar">, can be changed using the C<"style"> parameter. (The one
-parameter calling convention also still works.)
+C<"polar">, can be changed using the C<"style"> parameter.
+
+ $j->display_format(style => "polar");
+
+The one parameter calling convention also still works.
+
+ $j->display_format("polar");
There are two new display parameters.
-The first one is C<"format">, which is a sprintf()-style format
-string to be used for both parts of the complex number(s). The
-default is C<undef>, which corresponds usually (this is somewhat
-system-dependent) to C<"%.15g">. You can revert to the default by
-setting the format string to C<undef>.
+The first one is C<"format">, which is a sprintf()-style format string
+to be used for both numeric parts of the complex number(s). The is
+somewhat system-dependent but most often it corresponds to C<"%.15g">.
+You can revert to the default by setting the C<format> to C<undef>.
# the $j from the above example
$j->display_format('format' => '%.5f');
print "j = $j\n"; # Prints "j = -0.50000+0.86603i"
- $j->display_format('format' => '%.6f');
+ $j->display_format('format' => undef);
print "j = $j\n"; # Prints "j = -0.5+0.86603i"
Notice that this affects also the return values of the
C<display_format> methods: in list context the whole parameter hash
-will be returned, as opposed to only the style parameter value. If
-you want to know the whole truth for a complex number, you must call
-both the class method and the object method:
+will be returned, as opposed to only the style parameter value.
+This is a potential incompatibility with earlier versions if you
+have been calling the C<display_format> method in list context.
-The second new display parameter is C<"polar_pretty_print">, which can be
-set to true or false, the default being true. See above for what this
-means.
+The second new display parameter is C<"polar_pretty_print">, which can
+be set to true or false, the default being true. See the previous
+section for what this means.
=head1 USAGE
Died at...
For the C<csc>, C<cot>, C<asec>, C<acsc>, C<acot>, C<csch>, C<coth>,
-C<asech>, C<acsch>, the argument cannot be C<0> (zero). For the the
+C<asech>, C<acsch>, the argument cannot be C<0> (zero). For the
logarithmic functions and the C<atanh>, C<acoth>, the argument cannot
be C<1> (one). For the C<atanh>, C<acoth>, the argument cannot be
C<-1> (minus one). For the C<atan>, C<acot>, the argument cannot be
C<i> (the imaginary unit). For the C<atan>, C<acoth>, the argument
cannot be C<-i> (the negative imaginary unit). For the C<tan>,
C<sec>, C<tanh>, the argument cannot be I<pi/2 + k * pi>, where I<k>
-is any integer.
+is any integer. atan2(0, 0) is undefined, and if the complex arguments
+are used for atan2(), a division by zero will happen if z1**2+z2**2 == 0.
Note that because we are operating on approximations of real numbers,
these errors can happen when merely `too close' to the singularities
-listed above. For example C<tan(2*atan2(1,1)+1e-15)> will die of
-division by zero.
+listed above.
=head1 ERRORS DUE TO INDIGESTIBLE ARGUMENTS
=head1 BUGS
Saying C<use Math::Complex;> exports many mathematical routines in the
-caller environment and even overrides some (C<sqrt>, C<log>).
+caller environment and even overrides some (C<sqrt>, C<log>, C<atan2>).
This is construed as a feature by the Authors, actually... ;-)
All routines expect to be given real or complex numbers. Don't attempt to
=head1 AUTHORS
-Raphael Manfredi <F<Raphael_Manfredi@pobox.com>> and
-Jarkko Hietaniemi <F<jhi@iki.fi>>.
+Daniel S. Lewart <F<d-lewart@uiuc.edu>>
-Extensive patches by Daniel S. Lewart <F<d-lewart@uiuc.edu>>.
+Original authors Raphael Manfredi <F<Raphael_Manfredi@pobox.com>> and
+Jarkko Hietaniemi <F<jhi@iki.fi>>
=cut