Skip to main content

基础数学运算

Pythora 星球的人常常会把 Python 当做计算器来用,尤其是需要计算很多数据的时候,它比计算器还方便。Python 不仅支持基本的数学运算,如加法、减法、乘法和除法,还支持更复杂的数学操作,如指数运算、模运算、整数除法等。

整数 (Integers)

四则运算

Python 对于整数的运算与数学表达式基本一致,比如最简单的加减法运算:

print(3 + 2) # 输出 5
print(5 - 2) # 输出 3

Python 和大多数语言一样,使用星号 * 表示乘法,使用斜杠 / 表示除法。在 Python 2 中, 整数的 / 运算返回的依然还是整数结果,但在目前主要使用的 Python 3 中,整数的 / 运算结果可能是个浮点数。Python 3 使用双斜杠 // 表示整除运算;使用百分号 % 表示取模(余数)运算;使用双星号 ** 表示幂运算:

print(4 * 3) # 输出 12
print(8 / 3) # 输出 2.666...

print(8 // 3) # 输出 2
print(8 % 3) # 输出 2
print(2 ** 4) # 输出 16

+- 除了表示加法减法之外,还被用来表示正负数,比如 +2-5 等,正负号运算级别要高于其它运算符。那么大家猜一下运行下面的程序会出现什么结果?

print(2*-5)
print(1+++2)
print(1+-+-+-+-+2)

进制

Python 中可以使用不同进制来表示一个整数。最常见的十进制(Decimal)整数是没有特殊的前缀的,比如 42

二进制(Binary)整数使用 0b 或 0B 前缀来表示,比如 0b101010 表示十进制的 42。

八进制(Octal)整数使用 0o 或 0O 前缀来表示,比如 0o52 表示十进制的 42。

十六进制(Hexadecimal)整数使用 0x 前缀来表示,比如 0x2A 或 0x2a 表示十进制的 42。

使用 Python 的内置函数可以把一个整数转换成不同进制的字符串:

bin(x):将整数 x 转换为二进制字符串。 oct(x):将整数 x 转换为八进制字符串。 hex(x):将整数 x 转换为十六进制字符串。 int(string, base):将给定进制的字符串转换为十进制整数。

比如:

x = 42
print(bin(x)) # 输出 '0b101010'
print(oct(x)) # 输出 '0o52'
print(hex(x)) # 输出 '0x2a'

y = '0b101010'
print(int(y, 2)) # 输出 42

用函数 int() 可以把其它类型的数据转换成整数。

浮点数 (Floats)

四则运算

浮点数就是我们常说的小数,实数。它的计算与整数类似,但结果可能带有小数,比如:

print(3.0 + 2.5)  # 输出 5.5
print(5.5 - 2.5) # 输出 3.0
print(4.0 * 3.0) # 输出 12.0
print(8.0 / 3.0) # 输出 2.666...

表示法

浮点数常常会比较大,数位非常多,为了阅读方便,程序中可能会使用一些不同的表示方法表示浮点数。最常规表示法是直接使用小数点表示浮点数。例如:3.14, 0.001, 4.0 等。 另一种常用的表示方法是科学计数法,使用 e 或 E 来表示 10 的指数。例如: 2.5e2 表示 2.5×102=250.02.5 × 10^2 = 250.0,1.23E-3 表示 1.23×103=0.001231.23 × 10^{-3} = 0.00123

特殊值

Python 的浮点数有几个特殊值: inf、-inf 和 nan,它们分别代表正无穷、负无穷和非数字值(Not a Number)。这些特殊值的存在是为了遵循 IEEE 754 浮点数标准。

这些特殊值的运算遵循如下规则:

inf + 任何正数 = inf inf - 任何正数 = inf -inf + 任何负数 = -inf -inf - 任何负数 = -inf inf + -inf = nan (正无穷加负无穷是未定义的,所以结果是nan) inf * 0 = nan (无穷乘以 0 是未定义的)

比如下面的程序:

positive_infinity = float("inf")
negative_infinity = float("-inf")
print(positive_infinity + negative_infinity) # 输出 nan

在上面的程序里 float() 是一个函数,用于将字符串或其他数值转换为浮点数,比如:

print(float(7))       # 输出 7.0
print(float("3.14")) # 输出 3.14

误差

浮点数的计算可能会导致一些舍入误差。这是由于计算机内部使用二进制浮点数表示法,某些十进制小数不能精确地表示为二进制小数。例如:

print(0.1 + 0.2)  # 输出 0.30000000000000004 而不是 0.3

由于这种舍入误差,对浮点数进行相等性测试时需要特别小心。通常建议使用某个小的容差值,而不是直接比较两个浮点数是否完全相等。

复数 (Complex Numbers)

复数包含实部和虚部,虚部后面通常有一个“j”或“J”。比如:

print((3 + 4j) + (2 - 3j))  # 输出 5 + j
print((3 + 4j) - (2 - 3j)) # 输出 1 + 7j
print((3 + 4j) * (2 - 3j)) # 输出 18 - 1j
print((3 + 4j) / (2 - 3j)) # 输出 -0.46153846153846156 + 1.3076923076923077j

更复杂的数学运算

除了基本的算术运算外,Python 还提供了许多内置函数来支持数值计算,如 abs(), round(), max(), min(), sum() 等。这些函数都是比较直观的,与数据运算中使用的函数保持一致,一看名字就知道它们是做什么的。例如:

print(abs(-5))      # 输出 5,表示绝对值计算
print(around(2.65)) # 输出 3,表示四舍五入的值

Python 还支持复数运算和一些更高级的数学函数,使其成为处理数学问题的强大工具。在科学计算、数据分析等领域,Python 尤其受欢迎,部分原因是因为它的这种灵活性和强大的数学运算能力。Python 的复杂数学函数多在自带的 math 和 statistics 标准库中,后文会对它们做详细介绍。

布尔值 (Booleans)

布尔值只有两种可能,即 True 和 False。它们实际上是整数的子类型,其中 True 为 1,False 为 0。

数据比较

在 Python 中,比较数值大小,是否相等的操作,会返回一个布尔值结果。以下是进行数值大小比较的常用运算符:

大于号 > 检查左边的值是否大于右边的值;小于号 < 检查左边的值是否小于右边的值;大于或等于号 >= 检查左边的值是否大于或等于右边的值;小于或等于 <= 检查左边的值是否小于或等于右边的值;等于 == 检查两个值是否相等; 不等于 != 检查两个值是否不相等。下面是一些示例:

print(5 > 3)    # 输出 True
print(2 < 8) # 输出 True
print(7 >= 7) # 输出 True
print(4 <= 10) # 输出 True
print(6 == 6) # 输出 True
print(5 != 5) # 输出 False

编程序时,最容易出现的一个错误就是在判断两个数是否相等时,把相等检查的双等号 ==,写成赋值用的单等号 =。不过 Python 还好,通常会报告这样的错误,不像某些语言会悄无声息继续执行错误代码。

这些比较运算符不仅可以用于数值类型(整数、浮点数等),还可以用于其他可比较的数据类型,例如字符串、列表和其他序列类型。当比较这些非数值类型时,Python 通常使用字典序(也称为词典顺序)来确定顺序。

例如,在比较字符串时,"apple" < "banana" 的结果是 True,因为在字典中 "apple" 出现在 "banana" 之前。

需要注意的是,进行数值比较时应确保参与比较的是同一类型或者是可以直接比较的类型。尽管 Python 通常可以自动在不同的数值类型之间进行转换(例如,在整数和浮点数之间),但在某些情况下,直接比较可能会导致不可预期的结果。

链式比较

有时候,我们可以使用一种非常直观的方式来进行多个条件的比较,也就是链式比较。比如,我们需要判断 x > 1 并且 x < 2 ,那么可以直接写成 1 < x < 2。这种链式比较的工作原理是:Python 会按照从左到右的顺序评估每个比较操作。比如对于 1 < x < 2,会首先会检查 1 < x 是否为真。如果为真,接着检查 x < 2。只有当这两个条件都为真时,整个表达式才会返回真。如果任何一个条件失败,则整个表达式立即返回假。

我们可以使用任何比较运算符,比如大于(>)、小于等于(<=)、大于等于(>=)等,来构建类似的链式比较。这种方式也不仅限于两个条件的比较,例如,a < b < c < d 也是完全有效的,它会检查 b 是否大于 a 且小于 c,以及 c 是否小于 d。比如:

x = 21
y = 25

print(20 < x < y < 30) # 输出 True

除了比较大小,其它一些运算符可以用在链式比较中,比如这个使用列表的例子,但那样通常会降低程序可读性,不推荐使用。

浮点数相等判断

由于浮点数的表示和计算可能导致微小的舍入误差,直接使用等于运算符 == 来判断两个浮点数是否相等可能会得到不预期的结果。例如,计算 0.1 + 0.2 的结果并不完全等于 0.3,因为存在小的浮点舍入误差。

为了准确地判断两个浮点数是否"相等",一种常用的方法是判断两数之间的差值是否小于某个非常小的值(通常称为"容差"或"epsilon")。

以下是一个简单的示例,说明如何使用这种方法判断两个浮点数是否相等:

epsilon=1e-9

x = 0.1 + 0.2
y = 0.3

print(x == y) # 输出 False
print(abs(x - y) < epsilon) # 输出 True

在上面的示例中,abs() 还一个函数,它可以返回输入数据的绝对值。比较的逻辑是:如果 x 和 y 之间的差值小于 epsilon,则认为它们是"相等"的。选择合适的 epsilon 值是很重要的。对于大多数应用场景,1e-9 或 1e-12 通常足够了。

特殊值的比较

nan 与任何值(包括其自身)的比较都会返回 False。 nan 与任何数字进行算术运算都会返回 nan。 两个 nan 值之间的比较使用 == 会返回 False,但可以使用 math.isnan() 函数来检查一个值是否为 nan。

positive_infinity = float("inf")
negative_infinity = float("-inf")
not_a_number = float("nan")

print(positive_infinity == positive_infinity) # 输出 True
print(positive_infinity == positive_infinity + 100) # 输出 True
print(not_a_number == not_a_number) # 输出 False

变量指针的比较

上文提到的比较都是针对数据的比较,比如 a = 5; b = 5 那么就一定会有 a == b 为 True。但是,前文介绍过,不同变量,是可以指向同一个数据的。那么我们怎么判断,变量究竟是指向了同一个数据,还是指向了值相等的不同数据呢?

首先,我们可以使用 id() 函数得到一个数据的内存地址。如果内存地址相同,那么它们指向的就是同一个数据:

a = None
b = [1,2]
c = [1,2]

print(id(None)) # 输出数据 None 的地址
print(id(a)) # 输出的地址与上一行相同
print(id(b)) # 这是一个不同的地址
print(id(c)) # 这又是一个不同的地址

上面程序每次运行的结果可能都不同,因为程序每次会把数据放到不同的内存地址中去。

我们也可以使用 is 操作符直接检查两个变量是否引用内存中的同一个数据,也就是两个数据的地址是否相同。这与 == 操作符不同,后者用于比较两个数据的值是否相等。x is y 就等价于 id(x) == id(y)。类似的,可以使用 is not 来确认两个变量不在同一内存地址。比如:

x = [1,2]    
y = [1,2]
z = x

print(x == y) # 输出 True 表示 x 和 y 的值相等
print(x is y) # 输出 False 表示 x 和 y 指向了不同的数据
print(x is z) # 输出 True 表示 x 和 z 指向了同一个数据
print(x is not y) # 输出 True 表示 x 和 y 指向了不同的数据

一般来说,如果有 x is y 则一定会有 x == y,反之则不一定。只有一个特例,nan,因为 nan == nan 返回 False 是被硬性规定的,实际上数据可能是同一个数据:

not_a_number = float("nan")

print(not_a_number == not_a_number) # 输出 False
print(not_a_number is not_a_number) # 输出 True
print([not_a_number] == [not_a_number]) # 输出 True

在 Python 中,有些数据是确定只有全局一份的,比如 None 这个数据,它是 NoneType 类型的唯一数据,通常用于初始化变量、表示默认状态、进行空值检查等场景。这个数据在全局就只有一份,任何值为 None 的变量,都一定是指向了这个数据。所以,在 Python 判断一个数据是否为 None,通常不会使用 == 操作符,而是会使用 is 操作符。

对于不可变数据类型的数据,Python 通常会优化代码,尽量重用数据。因此,即使两个变量看似独立地定义了相同的值,它们很可能也会指向同一个对象,比如:

x = "Pythora 星球"
y = "Pythora" + " " + "星球"

print(x is y) # 多数情况下返回 True

上面代码多数情况下会返回 True,但 Python 并不总能把代码优化到最佳,有些情形下,它会为两个值相等的数据分配不同的内存地址。因此,需要比较两个变量是否相等,一定要使用 == 操作符,而不是 is

对于可变数据类型的数据,Python 通常只能为两份数据分配不同的内存空间,以防止任何一个数据变化影响到其它数据。有时候,当前一个数据可以被移除内存时,Python 也会重用那块内存,可能会造成两个可变数据类型的数据有相同的内存空间。

逻辑计算

布尔运算是基于两个值 True 和 False 进行的逻辑操作。布尔运算常常用于比较和控制流语句中,例如 if、while 和 for 语句。

以下是 Python 中有三个基本的布尔运算符:

  • and 运算符: 当两边的操作数都为 True 时,结果才为 True。
  • or 运算符: 只要两边的操作数中有一个为 True,结果就为 True。
  • not 运算符: 用于对布尔值进行取反。

例如:

print(True and True)   # 结果为 True
print(True and False) # 结果为 False
print(True or False) # 结果为 True
print(False or False) # 结果为 False
print(not True) # 结果为 False
print(not False) # 结果为 True

布尔作为数值

Python 中,如果把布尔值与加减乘除等算数符号一起使用,会自动把 True 转换成 1,把 False 转换成 0,比如:

print(True + 1)       # 结果为 2
print(False + 1) # 结果为 1
print(True - False) # 结果为 1

其它数据作为布尔值

在使用逻辑运算时,False、None、0、空集合、空序列等会被视为假;其它值都被视为真。比如:

print(not "")     # 输出: True
print(not "abc") # 输出: False
print(not 0) # 输出: True
print(not 3) # 输出: False

and 和 or 运算稍微复杂一点:

  • 做 and 运算时,如果第一数据为假则返回第一个数据,否则返回第二个数据;
  • 做 and 运算时,如果第一数据为真则返回第一个数据,否则返回第二个数据;

比如:

print(1 or 0)      # 输出: 1
print(2 and 0) # 输出: 0
print(0 or []) # 输出: []
print(2 and []) # 输出: []

需要注意一下 not 运算符的优先级,在很多语言中,not 运算的优先级是最高的,但 Python 中 is not 的优先级更高。比如:

print(False is not None)     # 输出: True
print(False is (not None)) # 输出: False

复合赋值运算

复合赋值运算符是 Python 中的一种简化赋值操作的方式,它将赋值操作和其他操作(如加法、减法、乘法等)结合在一起。复合赋值运算符通常用于简化代码和提高可读性。比如加法赋值运算符是 +=, 它表示把运算符两遍的数据或变量相加,再把结果赋值给运算符左边的变量。

a = 5
a += 2 # 等同于 a = a + 2
print(a) # 输出 7

Python 中的常用符合赋值运算符包括: += : 加法赋值; -= : 减法赋值; *= : 乘法赋值; /= : 除法赋值; %= : 取模赋值(返回除法的余数); //= : 整除赋值(返回除法的商,忽略余数); **= : 幂赋值; &= : 按位与赋值; |= : 按位或赋值; ^= : 按位异或赋值; <<= : 左移赋值; >>= : 右移赋值。

有些语言中,有种很常用变量一元运算符,比如 i++ 后缀递增、递减运算符。但 Python 不支持这样的运算符,只能使用 i += 1 进行计算。