6.5. math — 数学函数

未匹配的标注

目的:提供数学专属的函数

math 库提供关于复杂浮点数学运算的函数,比如对数函数,三角函数。这些函数通常是可以在原生平台的 C 语言库中可以找到的 IEEE 函数。

特殊常数

许多数学运算都需要用到一些特殊的常数, math 中提供的常数有: π (圆周率), e(自然底数), nan (用以表示“不明确的数值结果”),以及 inf(无穷大)。

math_constants.py

import math

print('  π: {:.30f}'.format(math.pi))
print('  e: {:.30f}'.format(math.e))
print('nan: {:.30f}'.format(math.nan))
print('inf: {:.30f}'.format(math.inf))

π 和 e 的精度由当前平台 C 语言库的浮点精度决定。

$ python3 math_constants.py

  π: 3.141592653589793115997963468544
  e: 2.718281828459045090795598298428
nan: nan
inf: inf

关于异常值的测试

浮点运算中可能出现两种异常值。第一种是 inf (无穷大),它出现在用 double 存储一个绝对值过大(溢出)的数。

math_isinf.py

import math

print('{:^3} {:6} {:6} {:6}'.format(
    'e', 'x', 'x**2', 'isinf'))
print('{:-^3} {:-^6} {:-^6} {:-^6}'.format(
    '', '', '', ''))

for e in range(0, 201, 20):
    x = 10.0 ** e
    y = x * x
    print('{:3d} {:<6g} {:<6g} {!s:6}'.format(
        e, x, y, math.isinf(y),
    ))

在这个例子中,随着 x 值的不断增大, x 平方的实际值将无法被存储为一个正常的 double 值。这时, x 平方的将被存储为无穷大。

$ python3 math_isinf.py

 e  x      x**2   isinf
--- ------ ------ ------
  0 1      1      False
 20 1e+20  1e+40  False
 40 1e+40  1e+80  False
 60 1e+60  1e+120 False
 80 1e+80  1e+160 False
100 1e+100 1e+200 False
120 1e+120 1e+240 False
140 1e+140 1e+280 False
160 1e+160 inf    True
180 1e+180 inf    True
200 1e+200 inf    True

但并不是所有结果过大的浮点运算都会给出 inf。特别是计算一个浮点数的指数值时,过大的结果会给出 OverflowError 而不是 inf

math_overflow.py

x = 10.0 ** 200

print('x    =', x)
print('x*x  =', x * x)
print('x**2 =', end=' ')
try:
    print(x ** 2)
except OverflowError as err:
    print(err)

C Python 中不同的实现导致了这个差异。

$ python3 math_overflow.py

x    = 1e+200
x*x  = inf
x**2 = (34, 'Result too large')

使用无穷大进行除法运算是未定义行为,也就是说这会返回一个 nan (不是一个数)。

math_isnan.py

import math

x = (10.0 ** 200) * (10.0 ** 200)
y = x / x

print('x =', x)
print('isnan(x) =', math.isnan(x))
print('y = x / x =', x / x)
print('y == nan =', y == float('nan'))
print('isnan(y) =', math.isnan(y))

nan 将不会和任何数相等,甚至是它本身。
isnan() 可以被用来检查一个数是不是 nan

$ python3 math_isnan.py

x = inf
isnan(x) = False
y = x / x = nan
y == nan = False
isnan(y) = True

可以用 isfinite() 来检查一个数是不是 infnan

math_isfinite.py

import math

for f in [0.0, 1.0, math.pi, math.e, math.inf, math.nan]:
    print('{:5.2f} {!s}'.format(f, math.isfinite(f)))

对于所有异常值,isfinite() 都将返回假,对其他情况,则返回真。

$ python3 math_isfinite.py

 0.00 True
 1.00 True
 3.14 True
 2.72 True
  inf False
  nan False

浮点数的比较

由于每一次浮点运算都有可能引入舍入误差,所以浮点数的比较是一个常常容易犯错的地方。isclose() 函数通过两个待比较数的相对会绝对误差来完成比较这个操作。这个函数的行为等价于下面这个公式。

abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

默认情况下, isclose() 比较两个数的相对误差,这个阈值被定在 1e-09。也就是说,如果待比较的的两个数的差与其中较大绝对值的商小于等于 1e-09,则认为这两个数是相等的。通过传递参数 rel_tol 到函数 isclose() 可以来控制这个容差。在下面这个例子中,我们把这个容差设为 10%。

math_isclose.py

import math

INPUTS = [
    (1000, 900, 0.1),
    (100, 90, 0.1),
    (10, 9, 0.1),
    (1, 0.9, 0.1),
    (0.1, 0.09, 0.1),
]

print('{:^8} {:^8} {:^8} {:^8} {:^8} {:^8}'.format(
    'a', 'b', 'rel_tol', 'abs(a-b)', 'tolerance', 'close')
)
print('{:-^8} {:-^8} {:-^8} {:-^8} {:-^8} {:-^8}'.format(
    '-', '-', '-', '-', '-', '-'),
)

fmt = '{:8.2f} {:8.2f} {:8.2f} {:8.2f} {:8.2f} {!s:>8}'

for a, b, rel_tol in INPUTS:
    close = math.isclose(a, b, rel_tol=rel_tol)
    tolerance = rel_tol * max(abs(a), abs(b))
    abs_diff = abs(a - b)
    print(fmt.format(a, b, rel_tol, abs_diff, tolerance, close))

0.10.09 被认为相等(0.1 的相对容差)是因为在浮点运算下,(0.1-0.09)/0.1=0.10000000000000009,结果比 0.1 略大。

$ python3 math_isclose.py

   a        b     rel_tol  abs(a-b) tolerance  close
-------- -------- -------- -------- -------- --------
 1000.00   900.00     0.10   100.00   100.00     True
  100.00    90.00     0.10    10.00    10.00     True
   10.00     9.00     0.10     1.00     1.00     True
    1.00     0.90     0.10     0.10     0.10     True
    0.10     0.09     0.10     0.01     0.01    False

如果使用绝对容差进行比较,可以向函数传递 abs_tol 参数。

math_isclose_abs_tol.py

import math

INPUTS = [
    (1.0, 1.0 + 1e-07, 1e-08),
    (1.0, 1.0 + 1e-08, 1e-08),
    (1.0, 1.0 + 1e-09, 1e-08),
]

print('{:^8} {:^11} {:^8} {:^10} {:^8}'.format(
    'a', 'b', 'abs_tol', 'abs(a-b)', 'close')
)
print('{:-^8} {:-^11} {:-^8} {:-^10} {:-^8}'.format(
    '-', '-', '-', '-', '-'),
)

for a, b, abs_tol in INPUTS:
    close = math.isclose(a, b, abs_tol=abs_tol)
    abs_diff = abs(a - b)
    print('{:8.2f} {:11} {:8} {:0.9f} {!s:>8}'.format(
        a, b, abs_tol, abs_diff, close))

对于绝对容差,两个待比较数差的绝对值需要小于所设定的容差。

$ python3 math_isclose_abs_tol.py

   a          b      abs_tol   abs(a-b)   close
-------- ----------- -------- ---------- --------
    1.00   1.0000001    1e-08 0.000000100    False
    1.00  1.00000001    1e-08 0.000000010     True
    1.00 1.000000001    1e-08 0.000000001     True

关于涉及 nan 和 inf 的比较是两种特殊情况。

math_isclose_inf.py

import math

print('nan, nan:', math.isclose(math.nan, math.nan))
print('nan, 1.0:', math.isclose(math.nan, 1.0))
print('inf, inf:', math.isclose(math.inf, math.inf))
print('inf, 1.0:', math.isclose(math.inf, 1.0))

nan 不接近任何数(包括 nan 本身), inf 只和自己比较时为真。

$ python3 math_isclose_inf.py

nan, nan: False
nan, 1.0: False
inf, inf: True
inf, 1.0: False

浮点数到整数的转换

math 库包括了三个转换实数到整数的函数。每个函数都有不同的功能和独特的运用情景。

最简单的是 trunc() 函数,它直接把把浮点数的小数部分截去。 floor() 对输入向下取整,即返回输入浮点数的邻近整数中较小的那个。ceil()floor() 相反,对输入向上取整,即即返回输入浮点数的邻近整数中较大的那个。

math_integers.py

import math

HEADINGS = ('i', 'int', 'trunk', 'floor', 'ceil')
print('{:^5} {:^5} {:^5} {:^5} {:^5}'.format(*HEADINGS))
print('{:-^5} {:-^5} {:-^5} {:-^5} {:-^5}'.format(
    '', '', '', '', '',
))

fmt = '{:5.1f} {:5.1f} {:5.1f} {:5.1f} {:5.1f}'

TEST_VALUES = [
    -1.5,
    -0.8,
    -0.5,
    -0.2,
    0,
    0.2,
    0.5,
    0.8,
    1,
]

for i in TEST_VALUES:
    print(fmt.format(
        i,
        int(i),
        math.trunc(i),
        math.floor(i),
        math.ceil(i),
    ))

trunc() 函数等价与直接用 int

$ python3 math_integers.py

  i    int  trunk floor ceil
----- ----- ----- ----- -----
 -1.5  -1.0  -1.0  -2.0  -1.0
 -0.8   0.0   0.0  -1.0   0.0
 -0.5   0.0   0.0  -1.0   0.0
 -0.2   0.0   0.0  -1.0   0.0
  0.0   0.0   0.0   0.0   0.0
  0.2   0.0   0.0   0.0   1.0
  0.5   0.0   0.0   0.0   1.0
  0.8   0.0   0.0   0.0   1.0
  1.0   1.0   1.0   1.0   1.0

浮点数的其他表示方法

modf() 将一个浮点数分成小数和整数两个部分,并以一个元祖的形式返回结果。

math_modf.py

import math

for i in range(6):
    print('{}/2 = {}'.format(i, math.modf(i / 2.0)))

所有返回的数都是浮点数。

$ python3 math_modf.py

0/2 = (0.0, 0.0)
1/2 = (0.5, 0.0)
2/2 = (0.0, 1.0)
3/2 = (0.5, 1.0)
4/2 = (0.0, 2.0)
5/2 = (0.5, 2.0)

frexp() 返回一个浮点数的尾数和指数。这种表示方式更贴近浮点数在计算机中的底层表示,在某些情景下更为方便。

math_frexp.py

import math

print('{:^7} {:^7} {:^7}'.format('x', 'm', 'e'))
print('{:-^7} {:-^7} {:-^7}'.format('', '', ''))

for x in [0.1, 0.5, 4.0]:
    m, e = math.frexp(x)
    print('{:7.2f} {:7.2f} {:7d}'.format(x, m, e))

任何一个浮点数都可以写成 x = m * 2**e 的形式,frexp() 会返回输入值 x 所对应的 m 和 e

$ python3 math_frexp.py

   x       m       e
------- ------- -------
   0.10    0.80      -3
   0.50    0.50       0
   4.00    0.50       3

ldexp() 是 frexp() 的反函数。

math_ldexp.py

import math

print('{:^7} {:^7} {:^7}'.format('m', 'e', 'x'))
print('{:-^7} {:-^7} {:-^7}'.format('', '', ''))

INPUTS = [
    (0.8, -3),
    (0.5, 0),
    (0.5, 3),
]

for m, e in INPUTS:
    x = math.ldexp(m, e)
    print('{:7.2f} {:7d} {:7.2f}'.format(m, e, x))

和 frexp() 类似, ldexp() 函数则把输入的尾数 m 和指数 e 代入公式 x = m * 2**e, 返回一个浮点数 x

$ python3 math_ldexp.py

   m       e       x
------- ------- -------
   0.80      -3    0.10
   0.50       0    0.50
   0.50       3    4.00

正负号

一个数的绝对值就是其去除了正负号后的值,使用 fabs() 来计算一个浮点数的绝对值。

math_fabs.py

import math

print(math.fabs(-1.1))
print(math.fabs(-0.0))
print(math.fabs(0.0))
print(math.fabs(1.1))

实际上,float 的绝对值相当于一个正值。

$ python3 math_fabs.py

1.1
0.0
0.0
1.1

为了改变一个值的符号,或是要将一系列值设置为同一符号,又或者是比较两个值,使用 copysign() 来改变一个已知的好值的符号。

math_copysign.py

import math

HEADINGS = ('f', 's', '< 0', '> 0', '= 0')
print('{:^5} {:^5} {:^5} {:^5} {:^5}'.format(*HEADINGS))
print('{:-^5} {:-^5} {:-^5} {:-^5} {:-^5}'.format(
    '', '', '', '', '',
))

VALUES = [
    -1.0,
    0.0,
    1.0,
    float('-inf'),
    float('inf'),
    float('-nan'),
    float('nan'),
]

for f in VALUES:
    s = int(math.copysign(1, f))
    print('{:5.1f} {:5d} {!s:5} {!s:5} {!s:5}'.format(
        f, s, f < 0, f > 0, f == 0,
    ))

我们需要使用像 copysign() 这样的额外函数因为 nan 和 -nan 之间的直接比较将不会奏效。

$ python3 math_copysign.py

  f     s    < 0   > 0   = 0
----- ----- ----- ----- -----
 -1.0    -1 True  False False
  0.0     1 False False True
  1.0     1 False True  False
 -inf    -1 True  False False
  inf     1 False True  False
  nan    -1 False False False
  nan     1 False False False

常用计算

在内存中用浮点数来精确的表示一个实数是一件非常有挑战性的事情。实数中很多值无法被浮点数精确的表示。在一系列连续的浮点运算过程中也经常会引入这些表示误差。math 库中就提供了一个计算多个浮点数和的函数,这个函数非常高效,并且可以减少因计算导致的误差。

math_fsum.py

import math

values = [0.1] * 10

print('Input values:', values)

print('sum()       : {:.20f}'.format(sum(values)))

s = 0.0
for i in values:
    s += i
print('for-loop    : {:.20f}'.format(s))

print('math.fsum() : {:.20f}'.format(math.fsum(values)))

这里给出了十个 0.1,很显然,这些数的和应该是 1.0。但是因为 0.1 无法用浮点数来精确表示,所以直接将这些数相加会出现误差,用 fsun() 函数则可以避免这个问题。

$ python3 math_fsum.py

Input values: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
sum()       : 0.99999999999999988898
for-loop    : 0.99999999999999988898
math.fsum() : 1.00000000000000000000

factorial() (阶乘)是在排列组合计算中常用的一个函数。在数学里,我们吧一个整数 n 的阶乘写作 n!。我们可以用一种递归的方式来定义阶乘 n!=(n-1)! * n,这个递归的终止条件是 0! == 1

math_factorial.py

import math

for i in [0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.1]:
    try:
        print('{:2.0f} {:6.0f}'.format(i, math.factorial(i)))
    except ValueError as err:
        print('Error computing factorial({}): {}'.format(i, err))

factorial() 虽然在整数上有定义,但是可以输入一个浮点数,只要这个浮点数可以无损的被转换为整数。

$ python3 math_factorial.py

 0      1
 1      1
 2      2
 3      6
 4     24
 5    120
Error computing factorial(6.1): factorial() only accepts integral
 values

gamma() 函数和 factorial() 函数非常类似。对于整数 ngamma(n)=factorial(n-1)。两者的区别是: gamma() 函数对大部分实数(除了 0 和负整数)都是有定义的。

math_gamma.py

import math

for i in [0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6]:
    try:
        print('{:2.1f} {:6.2f}'.format(i, math.gamma(i)))
    except ValueError as err:
        print('Error computing gamma({}): {}'.format(i, err))

gamma(0) 在数学上是没有定义的,程序会出错,抛出异常。

$ python3 math_gamma.py

Error computing gamma(0): math domain error
1.1   0.95
2.2   1.10
3.3   2.68
4.4  10.14
5.5  52.34
6.6 344.70

lgamma()  返回 gamma 函数绝对值的自然对数。

math_lgamma.py

import math

for i in [0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6]:
    try:
        print('{:2.1f} {:.20f} {:.20f}'.format(
            i,
            math.lgamma(i),
            math.log(math.gamma(i)),
        ))
    except ValueError as err:
        print('Error computing lgamma({}): {}'.format(i, err))

相比于分开使用对数函数和 gamma 函数,直接使用 lgamma() 精度更高。

$ python3 math_lgamma.py

Error computing lgamma(0): math domain error
1.1 -0.04987244125984036103 -0.04987244125983997245
2.2 0.09694746679063825923 0.09694746679063866168
3.3 0.98709857789473387513 0.98709857789473409717
4.4 2.31610349142485727469 2.31610349142485727469
5.5 3.95781396761871651080 3.95781396761871606671
6.6 5.84268005527463252236 5.84268005527463252236

取模运算符(%)计算一个除法的余数(比如 5 % 2 = 1)。这个内置的运算符只适用于整数。但是对于很多浮点数的运算,直接使用 % 会引起精度的损失,而fmod() 精度更高。

math_fmod.py

import math

print('{:^4} {:^4} {:^5} {:^5}'.format(
    'x', 'y', '%', 'fmod'))
print('{:-^4} {:-^4} {:-^5} {:-^5}'.format(
    '-', '-', '-', '-'))

INPUTS = [
    (5, 2),
    (5, -2),
    (-5, 2),
]

for x, y in INPUTS:
    print('{:4.1f} {:4.1f} {:5.2f} {:5.2f}'.format(
        x,
        y,
        x % y,
        math.fmod(x, y),
    ))

另一个需要注意的问题是,两者的对于负数情况的取模运算的定义也有区别,给出的余数的符号会不同。

$ python3 math_fmod.py

 x    y     %   fmod
---- ---- ----- -----
 5.0  2.0  1.00  1.00
 5.0 -2.0 -1.00  1.00
-5.0  2.0  1.00 -1.00

gcd() 可以用来计算最大公约数。

math_gcd.py

import math

print(math.gcd(10, 8))
print(math.gcd(10, 0))
print(math.gcd(50, 225))
print(math.gcd(11, 9))
print(math.gcd(0, 0))

如果两个输入都是 0,则返回 0

$ python3 math_gcd.py

2
10
25
1
0

指数与对数

在经济学,数学以及其他科学中,指数增长是一种常见的模型。Python 中有内置的指数运算符("**"),但是 math 库中的函数 pow() 可以被用作一个参数传递给其他函数。

math_pow.py

import math

INPUTS = [
    # Typical uses
    (2, 3),
    (2.1, 3.2),

    # Always 1
    (1.0, 5),
    (2.0, 0),

    # Not-a-number
    (2, float('nan')),

    # Roots
    (9.0, 0.5),
    (27.0, 1.0 / 3),
]

for x, y in INPUTS:
    print('{:5.1f} ** {:5.3f} = {:6.3f}'.format(
        x, y, math.pow(x, y)))

当底数是 1,或者指数是 0.0 时,这个指数运算的结果总是 1.0。大部分涉及 nan 运算,返回的结果都是 nan。如果一个指数运算中,指数值小于 1pow() 函数实际上是做了开根运算。

$ python3 math_pow.py

  2.0 ** 3.000 =  8.000
  2.1 ** 3.200 = 10.742
  1.0 ** 5.000 =  1.000
  2.0 ** 0.000 =  1.000
  2.0 **   nan =    nan
  9.0 ** 0.500 =  3.000
 27.0 ** 0.333 =  3.000

由于求平方根是非常常用的一个运算,所有有一个专门的函数 sqrt() 来做开方运算。

math_sqrt.py

import math

print(math.sqrt(9.0))
print(math.sqrt(3))
try:
    print(math.sqrt(-1))
except ValueError as err:
    print('Cannot compute sqrt(-1):', err)

对于负数的开方,理论上是得到一个复数。但是 math 库不处理复数情况,所以这时会抛出一个 ValueError 的异常。

$ python3 math_sqrt.py

3.0
1.7320508075688772
Cannot compute sqrt(-1): math domain error

指数函数实际上就是在给定 xb 的情况下求方程 x = b ** y 中的 y。在默认情况下 log() 用自然底数 e 作为底数。也可以通过传递第二个参数的方式来指定一个其他的底数。

math_log.py

import math

print(math.log(8))
print(math.log(8, 2))
print(math.log(0.5, 2))

当输入小于 1 时,对数函数返回的值小于 0。

$ python3 math_log.py

2.0794415416798357
3.0
-1.0

这里一共有三种 log()。对于一个给定的浮点数和舍入误差,对于某些底数,log(x, b) 所给结果的精度很差。log10() 作用等价于 log(x, 10),但用了比log()更精确的算法。

math_log10.py

import math

print('{:2} {:^12} {:^10} {:^20} {:8}'.format(
    'i', 'x', 'accurate', 'inaccurate', 'mismatch',
))
print('{:-^2} {:-^12} {:-^10} {:-^20} {:-^8}'.format(
    '', '', '', '', '',
))

for i in range(0, 10):
    x = math.pow(10, i)
    accurate = math.log10(x)
    inaccurate = math.log(x, 10)
    match = '' if int(inaccurate) == i else '*'
    print('{:2d} {:12.1f} {:10.8f} {:20.18f} {:^5}'.format(
        i, x, accurate, inaccurate, match,
    ))

这用 * 在结尾处标出了结果不精确的几行。

$ python3 math_log10.py

i       x        accurate       inaccurate      mismatch
-- ------------ ---------- -------------------- --------
 0          1.0 0.00000000 0.000000000000000000
 1         10.0 1.00000000 1.000000000000000000
 2        100.0 2.00000000 2.000000000000000000
 3       1000.0 3.00000000 2.999999999999999556   *
 4      10000.0 4.00000000 4.000000000000000000
 5     100000.0 5.00000000 5.000000000000000000
 6    1000000.0 6.00000000 5.999999999999999112   *
 7   10000000.0 7.00000000 7.000000000000000000
 8  100000000.0 8.00000000 8.000000000000000000
 9 1000000000.0 9.00000000 8.999999999999998224   *

类似于 log10(), log2() 等价于计算 math.log(x, 2)

math_log2.py

import math

print('{:>2} {:^5} {:^5}'.format(
    'i', 'x', 'log2',
))
print('{:-^2} {:-^5} {:-^5}'.format(
    '', '', '',
))

for i in range(0, 10):
    x = math.pow(2, i)
    result = math.log2(x)
    print('{:2d} {:5.1f} {:5.1f}'.format(
        i, x, result,
    ))

依赖于底层的平台,使用针对以 2 为底数的对数函数,可以获得更高精度,以及更好的性能。

$ python3 math_log2.py

 i   x   log2
-- ----- -----
 0   1.0   0.0
 1   2.0   1.0
 2   4.0   2.0
 3   8.0   3.0
 4  16.0   4.0
 5  32.0   5.0
 6  64.0   6.0
 7 128.0   7.0
 8 256.0   8.0
 9 512.0   9.0

log1p() 用来计算牛顿-墨卡托级数(也就是 1+x 的自然对数)。

math_log1p.py

import math

x = 0.0000000000000000000000001
print('x       :', x)
print('1 + x   :', 1 + x)
print('log(1+x):', math.log(1 + x))
print('log1p(x):', math.log1p(x))

对于接近 0 的值,log1p() 给出的结果更加精确。因为这个函数在第一步加法的时候就对舍入误差进行了补偿。

$ python3 math_log1p.py

x       : 1e-25
1 + x   : 1.0
log(1+x): 0.0
log1p(x): 1e-25

exp() 被用来计算指数运算 (e**x)。

math_exp.py

import math

x = 2

fmt = '{:.20f}'
print(fmt.format(math.e ** 2))
print(fmt.format(math.pow(math.e, 2)))
print(fmt.format(math.exp(2)))

这个函数实际上是函数 math.pow() 的一个特殊情况(math.pow(math.e, x))。类似的,使用 exp() 会更加精确。

$ python3 math_exp.py

7.38905609893064951876
7.38905609893064951876
7.38905609893065040694

expm1() 是 log1p() 的反函数,用来计算 e**x - 1

math_expm1.py

import math

x = 0.0000000000000000000000001

print(x)
print(math.exp(x) - 1)
print(math.expm1(x))

就像 log1p() 函数一样,expm1() 函数也对较小的 x 做了特殊的处理。

$ python3 math_expm1.py

1e-25
0.0
1e-25

角度

虽然,角度是日常生活对角的大小的常用度量。但在数学和科学计算中,用的更多的是弧度。一弧度是描述的是弧长与半径相等的狐所张的圆心角的大小。

计算圆周的公式是 2πr,也就是说弧度实际上是描述了半径和弧长的对应关系。这个对应关系在三角函数以及微积分中经常出现,所以运用弧度制通常可以得到更为简洁的公式。

radians() 函数可以将角度转为弧度。

math_radians.py

import math

print('{:^7} {:^7} {:^7}'.format(
    'Degrees', 'Radians', 'Expected'))
print('{:-^7} {:-^7} {:-^7}'.format(
    '', '', ''))

INPUTS = [
    (0, 0),
    (30, math.pi / 6),
    (45, math.pi / 4),
    (60, math.pi / 3),
    (90, math.pi / 2),
    (180, math.pi),
    (270, 3 / 2.0 * math.pi),
    (360, 2 * math.pi),
]

for deg, expected in INPUTS:
    print('{:7d} {:7.2f} {:7.2f}'.format(
        deg,
        math.radians(deg),
        expected,
    ))

这个转换的公式是 rad = deg * π / 180

$ python3 math_radians.py

Degrees Radians Expected
------- ------- -------
      0    0.00    0.00
     30    0.52    0.52
     45    0.79    0.79
     60    1.05    1.05
     90    1.57    1.57
    180    3.14    3.14
    270    4.71    4.71
    360    6.28    6.28

degrees() 函数将弧度转为角度。

math_degrees.py

import math

INPUTS = [
    (0, 0),
    (math.pi / 6, 30),
    (math.pi / 4, 45),
    (math.pi / 3, 60),
    (math.pi / 2, 90),
    (math.pi, 180),
    (3 * math.pi / 2, 270),
    (2 * math.pi, 360),
]

print('{:^8} {:^8} {:^8}'.format(
    'Radians', 'Degrees', 'Expected'))
print('{:-^8} {:-^8} {:-^8}'.format('', '', ''))
for rad, expected in INPUTS:
    print('{:8.2f} {:8.2f} {:8.2f}'.format(
        rad,
        math.degrees(rad),
        expected,
    ))

其转换公式是 deg = rad * 180 / π

$ python3 math_degrees.py

Radians  Degrees  Expected
-------- -------- --------
    0.00     0.00     0.00
    0.52    30.00    30.00
    0.79    45.00    45.00
    1.05    60.00    60.00
    1.57    90.00    90.00
    3.14   180.00   180.00
    4.71   270.00   270.00
    6.28   360.00   360.00

三角函数

三角函数将角度和直角三角形中的直角边联系起来的函数。在很多涉及周期性的公式,如简谐运动,圆周运动,或者其他一些用到角度的情况,三角函数都是非常常用的函数。在标准库中,所有的三角函数的输入都是弧度。

选定直角三角形中的一个角 A正弦函数返回这个角的对边与斜边的比(sin A =对边/斜边)。余弦函数返回这个角的邻边与斜边的比(cos A =邻边/斜边)。正切函数返回这个角的对边与邻边的比(tan A = 对边/邻边)。

math_trig.py

import math

print('{:^7} {:^7} {:^7} {:^7} {:^7}'.format(
    'Degrees', 'Radians', 'Sine', 'Cosine', 'Tangent'))
print('{:-^7} {:-^7} {:-^7} {:-^7} {:-^7}'.format(
    '-', '-', '-', '-', '-'))

fmt = '{:7.2f} {:7.2f} {:7.2f} {:7.2f} {:7.2f}'

for deg in range(0, 361, 30):
    rad = math.radians(deg)
    if deg in (90, 270):
        t = float('inf')
    else:
        t = math.tan(rad)
    print(fmt.format(deg, rad, math.sin(rad), math.cos(rad), t))

一个角的正切值等于其正弦值与余弦值之比。由于 π/2、3π/2 的余弦值是 0,所以这些角的正切值是无穷大。

$ python3 math_trig.py

Degrees Radians  Sine   Cosine  Tangent
------- ------- ------- ------- -------
   0.00    0.00    0.00    1.00    0.00
  30.00    0.52    0.50    0.87    0.58
  60.00    1.05    0.87    0.50    1.73
  90.00    1.57    1.00    0.00     inf
 120.00    2.09    0.87   -0.50   -1.73
 150.00    2.62    0.50   -0.87   -0.58
 180.00    3.14    0.00   -1.00   -0.00
 210.00    3.67   -0.50   -0.87    0.58
 240.00    4.19   -0.87   -0.50    1.73
 270.00    4.71   -1.00   -0.00     inf
 300.00    5.24   -0.87    0.50   -1.73
 330.00    5.76   -0.50    0.87   -0.58
 360.00    6.28   -0.00    1.00   -0.00

给定点 (x, y),直角三角形[(0, 0), (x, 0), (xy)]的斜边长是 (x**2 + y**2) ** 1/2,可以用函数 hypot() 来计算。

math_hypot.py

import math

print('{:^7} {:^7} {:^10}'.format('X', 'Y', 'Hypotenuse'))
print('{:-^7} {:-^7} {:-^10}'.format('', '', ''))

POINTS = [
    # simple points
    (1, 1),
    (-1, -1),
    (math.sqrt(2), math.sqrt(2)),
    (3, 4),  # 3-4-5 triangle
    # on the circle
    (math.sqrt(2) / 2, math.sqrt(2) / 2),  # pi/4 rads
    (0.5, math.sqrt(3) / 2),  # pi/3 rads
]

for x, y in POINTS:
    h = math.hypot(x, y)
    print('{:7.2f} {:7.2f} {:7.2f}'.format(x, y, h))

对于单位圆上的点,hypot() 的值都是 1。

$ python3 math_hypot.py

   X       Y    Hypotenuse
------- ------- ----------
   1.00    1.00    1.41
  -1.00   -1.00    1.41
   1.41    1.41    2.00
   3.00    4.00    5.00
   0.71    0.71    1.00
   0.50    0.87    1.00

我们也可以用这个函数来计算任意两点之间的距离。

math_distance_2_points.py

import math

print('{:^8} {:^8} {:^8} {:^8} {:^8}'.format(
    'X1', 'Y1', 'X2', 'Y2', 'Distance',
))
print('{:-^8} {:-^8} {:-^8} {:-^8} {:-^8}'.format(
    '', '', '', '', '',
))

POINTS = [
    ((5, 5), (6, 6)),
    ((-6, -6), (-5, -5)),
    ((0, 0), (3, 4)),  # 3-4-5 triangle
    ((-1, -1), (2, 3)),  # 3-4-5 triangle
]

for (x1, y1), (x2, y2) in POINTS:
    x = x1 - x2
    y = y1 - y2
    h = math.hypot(x, y)
    print('{:8.2f} {:8.2f} {:8.2f} {:8.2f} {:8.2f}'.format(
        x1, y1, x2, y2, h,
    ))

这里,我们用两点在 xy 方向上的差把其中一个点移动到远点,保持两点的相对位置,再把另一个点的新坐标传入 hypot(),就可以得到结果。

$ python3 math_distance_2_points.py

   X1       Y1       X2       Y2    Distance
-------- -------- -------- -------- --------
    5.00     5.00     6.00     6.00     1.41
   -6.00    -6.00    -5.00    -5.00     1.41
    0.00     0.00     3.00     4.00     5.00
   -1.00    -1.00     2.00     3.00     5.00

math 库中也定义了反三角函数。

math_inverse_trig.py

import math

for r in [0, 0.5, 1]:
    print('arcsine({:.1f})    = {:5.2f}'.format(r, math.asin(r)))
    print('arccosine({:.1f})  = {:5.2f}'.format(r, math.acos(r)))
    print('arctangent({:.1f}) = {:5.2f}'.format(r, math.atan(r)))
    print()

1.57 约等于 π/2,也就是 90 度。这个角的正弦值是 1,余弦值是 0。

$ python3 math_inverse_trig.py

arcsine(0.0)    =  0.00
arccosine(0.0)  =  1.57
arctangent(0.0) =  0.00

arcsine(0.5)    =  0.52
arccosine(0.5)  =  1.05
arctangent(0.5) =  0.46

arcsine(1.0)    =  1.57
arccosine(1.0)  =  0.00
arctangent(1.0) =  0.79

双曲函数

双曲函数常常在电磁学,流体力学,狭义相对论即其他一些高等物理,高等数学的线性微分方程中出现。

math_hyperbolic.py

import math

print('{:^6} {:^6} {:^6} {:^6}'.format(
    'X', 'sinh', 'cosh', 'tanh',
))
print('{:-^6} {:-^6} {:-^6} {:-^6}'.format('', '', '', ''))

fmt = '{:6.4f} {:6.4f} {:6.4f} {:6.4f}'

for i in range(0, 11, 2):
    x = i / 10.0
    print(fmt.format(
        x,
        math.sinh(x),
        math.cosh(x),
        math.tanh(x),
    ))

就像用单位圆来定义余弦和正弦函数一样,可以用双曲函数的一支来定义双曲余弦函数和双曲正弦函数。

$ python3 math_hyperbolic.py

  X     sinh   cosh   tanh
------ ------ ------ ------
0.0000 0.0000 1.0000 0.0000
0.2000 0.2013 1.0201 0.1974
0.4000 0.4108 1.0811 0.3799
0.6000 0.6367 1.1855 0.5370
0.8000 0.8881 1.3374 0.6640
1.0000 1.1752 1.5431 0.7616

同样,math 库中也有反双曲函数 acosh()asinh() 以及 atanh()

特殊函数

在统计学中,常会用到高斯误差函数。

math_erf.py

import math

print('{:^5} {:7}'.format('x', 'erf(x)'))
print('{:-^5} {:-^7}'.format('', ''))

for x in [-3, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 3]:
    print('{:5.2f} {:7.4f}'.format(x, math.erf(x)))

对于误差函数,erf(-x) == -erf(x)

$ python3 math_erf.py

  x   erf(x)
----- -------
-3.00 -1.0000
-2.00 -0.9953
-1.00 -0.8427
-0.50 -0.5205
-0.25 -0.2763
 0.00  0.0000
 0.25  0.2763
 0.50  0.5205
 1.00  0.8427
 2.00  0.9953
 3.00  1.0000

互补误差函数是 1 - erf(x)

math_erfc.py

import math

print('{:^5} {:7}'.format('x', 'erfc(x)'))
print('{:-^5} {:-^7}'.format('', ''))

for x in [-3, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 3]:
    print('{:5.2f} {:7.4f}'.format(x, math.erfc(x)))

erfc() 函数的实现避免了 1 减一个较小的 erf() 值时的精度损失。

$ python3 math_erfc.py

  x   erfc(x)
----- -------
-3.00  2.0000
-2.00  1.9953
-1.00  1.8427
-0.50  1.5205
-0.25  1.2763
 0.00  1.0000
 0.25  0.7237
 0.50  0.4795
 1.00  0.1573
 2.00  0.0047
 3.00  0.0000

See also

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/math-mat...

译文地址:https://learnku.com/docs/pymotw/math-mat...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~