Math::Complex - 复数及相关数学函数
use Math::Complex;
$z = Math::Complex->make(5, 6);
$t = 4 - 3*i + $z;
$j = cplxe(1, 2*pi/3);
此程序包允许您创建和处理复数。默认情况下,Perl 仅限于实数,但额外的 use
语句带来了完全的复数支持,以及一组通常与复数相关和/或扩展到复数的完整数学函数。
如果您想知道复数是什么,它们被发明出来是为了能够解决以下方程式
x*x = -1
根据定义,解记为i(工程师使用j,因为i通常表示强度,但名称无关紧要)。数字i是一个纯虚数。
纯虚数的算术就像您对实数所期望的那样...您只需要记住
i*i = -1
因此您有
5i + 7i = i * (5 + 7) = 12i
4i - 3i = i * (4 - 3) = i
4i * 2i = -8
6i / 2i = 3
1 / i = -i
复数是既有实部又有虚部的数,通常记为
a + bi
其中a
是实部,b
是虚部。复数的算术很简单。你必须跟踪实部和虚部,但除此之外,用于实数的规则也适用
(4 + 3i) + (5 - 2i) = (4 + 5) + i(3 - 2) = 9 + i
(2 + i) * (4 - i) = 2*4 + 4i -2i -i*i = 8 + 2i + 1 = 9 + 2i
复数的图形表示可以在一个平面上(也称为复平面,但它实际上是一个 2D 平面)。数字
z = a + bi
是坐标为 (a, b) 的点。实际上,它将是从 (0, 0) 到 (a, b) 的向量。由此得出,两个复数的加法是一个向量加法。
由于 2D 平面中的点和复数之间存在双射(即映射是唯一的和可逆的),因此复数也可以用极坐标唯一地标识
[rho, theta]
其中rho
是到原点的距离,theta
是向量和x轴之间的角度。使用指数形式对此有一个表示法,即
rho * exp(i * theta)
其中i是上面介绍的著名的虚数。此形式和笛卡尔形式a + bi
之间的转换是立即的
a = rho * cos(theta)
b = rho * sin(theta)
也可以用此公式表示
z = rho * exp(i * theta) = rho * (cos theta + i * sin theta)
换句话说,它是向量在x和y轴上的投影。数学家称rho为范数或模,称theta为复数的幅角。z
的范数在此标记为abs(z)
。
极坐标表示法(也称为三角表示法)对于执行复数的乘法和除法非常方便,而笛卡尔表示法更适合加法和减法。实数在x轴上,因此y或theta为零或pi。
可以在实数上执行的所有常见操作都被定义为也可以对复数执行,并且仅仅是对实数上定义的操作的扩展。这意味着当没有虚部时,它们会保持其自然含义,前提是该数字在其定义集中。
例如,计算其参数的平方根的 sqrt
例程仅针对非负实数定义,并产生非负实数(它是从 R+ 到 R+ 的应用)。如果我们允许它返回一个复数,那么它可以扩展到负实数,成为从 R 到 C(复数集)的应用
sqrt(x) = x >= 0 ? sqrt(x) : sqrt(-x)*i
它还可以扩展为从 C 到 C 的应用,而它对 R 的限制通过使用以下定义表现为如上所定义
sqrt(z = [r,t]) = sqrt(r) * exp(i * t/2)
事实上,一个负实数可以记为 [x,pi]
(模数 x 始终是非负的,所以 [x,pi]
实际上是 -x
,一个负数),而上述定义说明
sqrt([x,pi]) = sqrt(x) * exp(i*pi/2) = [sqrt(x),pi/2] = sqrt(x)*i
这正是我们上面为负实数定义的内容。sqrt
只返回一个解:如果你想要两个解,请使用 root
函数。
所有定义在实数上的常见数学函数都扩展到了复数,它们共享在虚部为零时以照常方式工作的相同属性(否则,它不会被称为扩展,是吗?)。
在复数上可能的一个新操作是实数的恒等式,称为共轭,并在此处用数字上方的水平线或 ~z
表示。
z = a + bi
~z = a - bi
简单... 现在看
z * ~z = (a + bi) * (a - bi) = a*a + b*b
我们看到 z
的范数记为 abs(z)
,并定义为到原点的距离,也称为
rho = abs(z) = sqrt(a*a + b*b)
所以
z * ~z = abs(z) ** 2
如果 z 是一个纯实数(即 b == 0
),则上述产生
a * a = abs(a) ** 2
这是正确的(abs
对实数有常规含义,即表示绝对值)。此示例解释了为什么 z
的范数记为 abs(z)
:它将 abs
函数扩展到复数,但当复数实际上没有虚部时,它是我们知道的常规 abs
... 这事后证明了我们对范数使用 abs
符号的合理性。
给定以下符号
z1 = a + bi = r1 * exp(i * t1)
z2 = c + di = r2 * exp(i * t2)
z = <any complex or real number>
以下(重载)操作支持复数
z1 + z2 = (a + c) + i(b + d)
z1 - z2 = (a - c) + i(b - d)
z1 * z2 = (r1 * r2) * exp(i * (t1 + t2))
z1 / z2 = (r1 / r2) * exp(i * (t1 - t2))
z1 ** z2 = exp(z2 * log z1)
~z = a - bi
abs(z) = r1 = sqrt(a*a + b*b)
sqrt(z) = sqrt(r1) * exp(i * t/2)
exp(z) = exp(a) * exp(i * b)
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(y, x) = atan(y / x) # Minding the right quadrant, note the order.
atan2() 的复数参数所用的定义是
-i log((x + iy)/sqrt(x*x+y*y))
请注意,atan2(0, 0) 未定义。
以下额外操作支持实数和复数
Re(z) = a
Im(z) = b
arg(z) = t
abs(z) = r
cbrt(z) = z ** (1/3)
log10(z) = log(z) / log(10)
logn(z, n) = log(z) / log(n)
tan(z) = sin(z) / cos(z)
csc(z) = 1 / sin(z)
sec(z) = 1 / cos(z)
cot(z) = 1 / tan(z)
asin(z) = -i * log(i*z + sqrt(1-z*z))
acos(z) = -i * log(z + i*sqrt(1-z*z))
atan(z) = i/2 * log((i+z) / (i-z))
acsc(z) = asin(1 / z)
asec(z) = acos(1 / z)
acot(z) = atan(1 / z) = -i/2 * log((i+z) / (z-i))
sinh(z) = 1/2 (exp(z) - exp(-z))
cosh(z) = 1/2 (exp(z) + exp(-z))
tanh(z) = sinh(z) / cosh(z) = (exp(z) - exp(-z)) / (exp(z) + exp(-z))
csch(z) = 1 / sinh(z)
sech(z) = 1 / cosh(z)
coth(z) = 1 / tanh(z)
asinh(z) = log(z + sqrt(z*z+1))
acosh(z) = log(z + sqrt(z*z-1))
atanh(z) = 1/2 * log((1+z) / (1-z))
acsch(z) = asinh(1 / z)
asech(z) = acosh(1 / z)
acoth(z) = atanh(1 / z) = 1/2 * log((1+z) / (z-1))
arg、abs、log、csc、cot、acsc、acot、csch、coth、acosech、acotanh 分别有别名 rho、theta、ln、cosec、cotan、acosec、acotan、cosech、cotanh、acosech、acotanh。Re
、Im
、arg
、abs
、rho
和 theta
也可用作变异器。cbrt
仅返回一个解:如果您想要全部三个解,请使用 root
函数。
root 函数可用于计算某个复数的所有 n 个根,其中 n 是一个严格的正整数。有恰好 n 个这样的根,以列表形式返回。获取数学家称之为 j
的数字,使得
1 + j + j*j = 0;
只需编写
$j = (root(1, 3))[1];
z = [r,t]
的第 k 个根由下式给出
(root(z, n))[k] = r**(1/n) * exp(i * (t + 2*k*pi)/n)
您可以通过 root(z, n, k)
直接返回第 k 个根,索引从 零 开始,以 n - 1 结束。
还定义了 spaceship 数字比较运算符 <=>。为了确保其对实数的限制符合您的预期,比较首先在复数的实部上运行,并且仅当实部匹配时才比较虚部。
要创建复数,请使用
$z = Math::Complex->make(3, 4);
$z = cplx(3, 4);
如果您知道该数字的笛卡尔形式,或
$z = 3 + 4*i;
如果您愿意。要使用极坐标形式创建数字,请使用
$z = Math::Complex->emake(5, pi/3);
$x = cplxe(5, pi/3);
代替。第一个参数是模数,第二个参数是角度(以弧度为单位,整圆是 2*pi)。(助记符:e
用作极坐标形式中复数的符号。)
可以写
$x = cplxe(-3, pi/4);
但它将被静默转换为 [3,-3pi/4]
,因为模数必须是非负的(它表示复平面上到原点的距离)。
还可以将复数作为 make
、emake
、cplx
和 cplxe
的任一参数:将使用参数的适当分量。
$z1 = cplx(-2, 1);
$z2 = cplx($z1, 4);
new
、make
、emake
、cplx
和 cplxe
还将理解形式为
2-3i
-3i
[2,3]
[2,-3pi/4]
[2]
在这种情况下,将从字符串中解析出适当的笛卡尔和指数分量,并用于创建新的复数。虚部和 theta 分别默认为零。
new
、make
、emake
、cplx
和 cplxe
也将理解无参数的情况:这意味着纯零或 (0, 0)。
打印时,复数通常以其笛卡尔样式 a+bi 显示,但在极坐标样式 [r,t] 更合适的情况下,这是合理的。将复数转换为可显示的字符串的过程称为字符串化。
通过调用类方法 Math::Complex::display_format
并提供 "polar"
或 "cartesian"
作为参数,可以覆盖默认显示样式,该样式为 "cartesian"
。不提供任何参数将返回当前设置。
可以通过调用 display_format
方法按每个数字覆盖此默认设置。与之前一样,不提供任何参数将返回此数字的当前显示样式。否则,您指定的任何内容都将成为此特定数字的新显示样式。
例如
use Math::Complex;
Math::Complex::display_format('polar');
$j = (root(1, 3))[1];
print "j = $j\n"; # Prints "j = [1,2pi/3]"
$j->display_format('cartesian');
print "j = $j\n"; # Prints "j = -0.5+0.866025403784439i"
极坐标样式尝试强调诸如 k*pi/n(其中 n 是正整数,k 是 [-9, +9] 内的整数)之类的参数,这称为极坐标漂亮打印。
有关字符串化的反向操作,请参阅 make
和 emake
。
现在可以使用参数哈希而不是仅仅一个参数来调用 display_format
类方法和相应的 display_format
对象方法。
可以使用 "style"
参数更改旧的显示格式样式,该样式可以具有值 "cartesian"
或 "polar"
。
$j->display_format(style => "polar");
一个参数的调用约定仍然有效。
$j->display_format("polar");
有两个新的显示参数。
第一个是 "format"
,它是一个 sprintf()-style 格式字符串,用于复数的两个数字部分。这在某种程度上取决于系统,但通常对应于 "%.15g"
。您可以通过将 format
设置为 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' => undef);
print "j = $j\n"; # Prints "j = -0.5+0.86603i"
请注意,这也影响到 display_format
方法的返回值:在列表上下文中,将返回整个参数哈希,而不仅仅是样式参数值。如果您在列表上下文中调用 display_format
方法,则这与早期版本存在潜在的不兼容性。
第二个新的显示参数是 "polar_pretty_print"
,可以将其设置为 true 或 false,默认值为 true。有关其含义,请参见上一部分。
由于重载,对复数进行算术处理非常简单,几乎是透明的。
以下是一些示例
use Math::Complex;
$j = cplxe(1, 2*pi/3); # $j ** 3 == 1
print "j = $j, j**3 = ", $j ** 3, "\n";
print "1 + j + j**2 = ", 1 + $j + $j**2, "\n";
$z = -16 + 0*i; # Force it to be a complex
print "sqrt($z) = ", sqrt($z), "\n";
$k = exp(i * 2*pi/3);
print "$j - $k = ", $j - $k, "\n";
$z->Re(3); # Re, Im, arg, abs,
$j->arg(2); # (the last two aka rho, theta)
# can be used also as mutators.
如果单独导出,常量 pi
及其一些方便的倍数(pi2、pi4 和 pip2(pi/2)和 pip4(pi/4))也可用
use Math::Complex ':pi';
$third_of_circle = pi2 / 3;
浮点无穷大可以导出为子例程 Inf()
use Math::Complex qw(Inf sinh);
my $AlsoInf = Inf() + 42;
my $AnotherInf = sinh(1e42);
print "$AlsoInf is $AnotherInf\n" if $AlsoInf == $AnotherInf;
请注意,无穷大的字符串化形式在不同平台之间有所不同:例如,它可以是以下任何一个
inf
infinity
INF
1.#INF
或者它可以是其他内容。
还要注意,在某些平台上,尝试在算术运算中使用无穷大可能会导致 Perl 崩溃,因为使用无穷大会导致发送 SIGFPE 或其道德等价物。忽略此问题的方法是
local $SIG{FPE} = sub { };
除法 (/) 和以下函数
log ln log10 logn
tan sec csc cot
atan asec acsc acot
tanh sech csch coth
atanh asech acsch acoth
无法针对所有参数计算,因为这意味着除以零或取零的对数。这些情况会导致类似以下内容的致命运行时错误
cot(0): Division by zero.
(Because in the definition of cot(0), the divisor sin(0) is 0)
Died at ...
或
atanh(-1): Logarithm of zero.
Died at...
对于 csc
、cot
、asec
、acsc
、acot
、csch
、coth
、asech
、acsch
,参数不能为 0
(零)。对于对数函数和 atanh
、acoth
,参数不能为 1
(一)。对于 atanh
、acoth
,参数不能为 -1
(负一)。对于 atan
、acot
,参数不能为 i
(虚数单位)。对于 atan
、acoth
,参数不能为 -i
(负虚数单位)。对于 tan
、sec
、tanh
,参数不能为 pi/2 + k * pi,其中 k 是任意整数。atan2(0, 0) 未定义,如果将复数参数用于 atan2(),当 z1**2+z2**2 == 0 时,将发生除以零的情况。
请注意,由于我们对实数近似值进行操作,因此当仅仅“非常接近”上述奇点时,可能会发生这些错误。
make
和 emake
接受实数和复数参数。当它们无法识别参数时,它们将终止并显示以下错误消息
Math::Complex::make: Cannot take real part of ...
Math::Complex::make: Cannot take real part of ...
Math::Complex::emake: Cannot take rho of ...
Math::Complex::emake: Cannot take theta of ...
声明 use Math::Complex;
会在调用者环境中导出许多数学例程,甚至会覆盖一些例程(sqrt
、log
、atan2
)。实际上,作者认为这是一个特性... ;-)
所有例程都希望给定实数或复数。不要尝试使用 BigFloat,因为 Perl 目前没有规则来消除两个重载实体之间的“+”操作(例如)。
在 Cray UNICOS 中,存在一些奇怪的数值不稳定性,导致 root()、cos()、sin()、cosh()、sinh() 快速丢失精度。请注意。该错误可能存在于 UNICOS 数学库、UNICOS C 编译器、Math::Complex 中。无论如何,它不会在 Perl 运行的任何其他地方表现出来。
Daniel S. Lewart <lewart!at!uiuc.edu>、Jarkko Hietaniemi <jhi!at!iki.fi>、Raphael Manfredi <Raphael_Manfredi!at!pobox.com>、Zefram <[email protected]>
此库是免费软件;您可以在与 Perl 本身相同的条款下重新分发和/或修改它。