(三)数值运算与转换
1. 除法、整除与取余
把 135 分钟拆成小时和分钟,可以先算完整小时,再算剩余分钟:
# // 给出向下取整的商,% 给出余数。
total_minutes = 135
hours = total_minutes // 60
minutes = total_minutes % 60
print(hours, minutes)
# / 执行普通除法,得到浮点数。
print(total_minutes / 60)输出 2 15 和 2.25。这组整数满足 135 = 2 × 60 + 15。
负数也可以代入同一关系:
# 向下取整朝更小的整数移动,所以 -7 // 3 是 -3。
print(-7 // 3)
print(-7 % 3)
print((-7 // 3) * 3 + (-7 % 3))输出 -3、2、-7。int(-2.7) 得到 -2,它去掉小数部分;-7 // 3 则向下取整。这两个动作的方向可以用负数直接区分。
| 运算 | 写法 | 结果 |
|---|---|---|
| 加、减、乘 | 7 + 3、7 - 3、7 * 3 |
10、4、21 |
| 普通除法 | 7 / 3 |
约 2.3333 |
| 向下整除、取余 | 7 // 3、7 % 3 |
2、1 |
| 幂 | 2 ** 5 |
32 |
| 绝对值 | abs(-7) |
7 |
先乘除后加减,需要改变顺序时使用括号。幂的结合顺序和一元负号值得单独试一次:2 ** 3 ** 2 等于 2 ** (3 ** 2),而 -2 ** 2 等于 -(2 ** 2),写 (-2) ** 2 才得到 4。
Python 的整数可以随数值增长使用更多存储空间。大整数不会因为超出 32 位就自动绕回去,但处理更多位数也要做更多工作。后面的复杂度估算会把数据量与实际操作联系起来。
数字也能按不同进制书写。0b 开头表示二进制,0o 表示八进制,0x 表示十六进制;数字中的下划线只帮助分组阅读:
# 不同书写形式可以表示同一个整数,输出时默认显示十进制。
print(0b1010, 0o12, 0xA, 10)
print(1_000_000 == 1000000)
# e 后面是十的指数;2.5e3 表示 2.5 × 10**3。
print(2.5e3, 2.5e-3)
# divmod 同时返回整除的商与余数,先观察这两个结果的顺序。
print(divmod(135, 60))依次输出 10 10 10 10、True、2500.0 0.0025 和 (2, 15)。最后的两个值按“完整小时、剩余分钟”的顺序放在一起,第(九)节会说明这种元组结构与拆包。
2. 转换的是值,不是显示外观
# int 处理整数文本,float 处理带小数点的数值文本。
print(int("42"))
print(float("3.5"))
# str 把数字转成文本,此后 + 做字符串拼接。
print(str(42) + " 分钟")
# 第二个参数指定整数文本使用的进制。
print(int("101", 2))int("3.5") 会报 ValueError,因为这段文本不是整数的书写形式。可以先用 float("3.5") 得到浮点数,再根据任务决定是保留小数、取整还是四舍五入。转换步骤应对应要保存的数据含义。
需要保存实部和虚部时,Python 还提供复数。虚数单位写成 j,例如 2 + 3j;其中 3j 是数值的一部分:
# real 与 imag 分别读取复数的实部和虚部。
value = 2 + 3j
print(value.real, value.imag)
print(value + (1 - 1j))输出 2.0 3.0 和 (3+2j)。后面的 NumPy dtype 表也会出现复数类型,可以从这两个分量继续理解。
3. 浮点数与十进制金额
# 二进制浮点数不能精确保存所有十进制小数。
print(0.1 + 0.2)
# 格式化控制显示位数,不改变参与计算的原始值。
print(f"{0.1 + 0.2:.2f}")分别输出 0.30000000000000004 和 0.30。许多十进制小数换成二进制后是无限展开,存储时保留有限位数,因此运算会带来舍入误差。
按十进制记录金额时,可以直接从十进制文本创建 Decimal:
# from ... import ... 从标准库模块中取出指定工具。
from decimal import Decimal, ROUND_HALF_UP
# 从字符串建立十进制数,避免先经过 float 的近似值。
price = Decimal("19.90")
total = price * 3
print(total)
# quantize 把结果按给定的小数位数和舍入方式量化。
print(Decimal("2.345").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))输出 59.70 和 2.35。这里的 Decimal、ROUND_HALF_UP 由标准库 decimal 提供,不需要 pip 安装。round(2.5) 得到 2,round(3.5) 得到 4,它在这两个恰好居中的例子中取偶数;金额怎样舍入应写成明确操作。decimal 文档
4. 位运算与二进制位置
把整数 5 写成二进制是 0101,整数 3 是 0011。位运算逐个二进制位置处理:& 在两位都为 1 时得到 1,| 在至少一位为 1 时得到 1,^ 在两位不同时得到 1。
# 0b 表示二进制;:04b 把整数按二进制显示,并补足到至少 4 位。
a = 0b0101
b = 0b0011
print(f"{a & b:04b}")
print(f"{a | b:04b}")
print(f"{a ^ b:04b}")
# 左移一位相当于乘 2;这个正整数右移一位相当于整除 2。
print(a << 1, a >> 1)
# Python 整数的按位取反满足 ~a == -a - 1。
print(~a)
# 与 1111 按位与,取出结果的低 4 位来观察。
print(f"{(~a) & 0b1111:04b}")输出依次是 0001、0111、0110、10 2、-6 和 1010。左移后,原来的第 0 位走到第 1 位;右移则把低位移出。~5 的整数结果是 -6,最后一行再把它的低四位取出显示。
这组操作处理整数的二进制位。下一节的 and、or 按整个对象的真假选择结果;后面的集合和 NumPy 又会为同样的符号提供与自身数据对应的操作。整数位运算
5. 复合赋值与递增递减
5.1 运算后更新原来的名称
累计阅读时长时,前面写过 minutes = minutes + 10。也可以写成 minutes += 10:取出原来的分钟数,加上 10,再让 minutes 保存结果。这种把运算和赋值写在一起的形式叫复合赋值,Python 文档称为增强赋值。
# += 读取原来的分钟数,完成加法,再把结果保存到 minutes。
minutes = 20
minutes += 15
print(minutes)
# 后面的每条语句都接着上一条的结果继续更新。
minutes -= 5
minutes *= 2
minutes /= 4
print(minutes)先输出 35。随后依次得到 30、60、15.0,最后输出 15.0。/= 仍执行普通除法,即使整除也会得到浮点数;//= 则使用前面讲过的向下取整除法。
其他运算也有对应写法。下表中的 x 表示已经保存的数值,右边以 2 为例:
| 写法 | 数值更新的读法 |
|---|---|
x += 2 |
加上 2,再保存结果 |
x -= 2 |
减去 2,再保存结果 |
x *= 2 |
乘以 2,再保存结果 |
x /= 2 |
除以 2,再保存普通除法的结果 |
x //= 2 |
除以 2,再保存向下取整的商 |
x %= 2 |
保存除以 2 的余数 |
x **= 2 |
保存原数的平方 |
x &= 2 |
与整数 2 按位与,再保存结果 |
x |= 2 |
与整数 2 按位或,再保存结果 |
x ^= 2 |
与整数 2 按位异或,再保存结果 |
x <<= 2 |
左移两位,再保存结果 |
x >>= 2 |
右移两位,再保存结果 |
这些写法本身是一条赋值语句。想更新后输出,就先写 x += 2,下一行再写 print(x);不能把它塞进 print(x += 2)。到了列表部分,还会看到同样的 +=、*= 怎样直接修改共享对象。增强赋值
5.2 加一、减一与连续正负号
Python 没有 C/C++ 中的 ++、-- 自增自减运算符。把计数加一写成 count += 1,减一写成 count -= 1:
# 连续的正号或负号仍是对值做一元运算,不会更新整数 count。
count = 5
print(++count, --count)
print(count)
# 真正更新计数时,分别使用 += 1 与 -= 1。
count += 1
print(count)
count -= 1
print(count)依次输出 5 5、5、6、5。这里 ++count 被读成 +(+count),--count 被读成 -(-count):两次取正,或两次取负,都没有把新值赋回 count。写在后面的 count++、count-- 则会报 SyntaxError,表示这种写法不符合 Python 语法。一元正负号