7 和 2:+ 9、- 5、* 14、/ 3.5、// 3、% 1、幂 49。
四个坑:真除法出浮点、负数整除向下取整、0.1+0.2 差 5.55e-17、
2**3**2 是 512 不是 64。
上一节把数据装进变量,这一节让它算起来。运算本身不难,难的是那四个「看着对、其实错」的地方。
一、七个运算符,一次看全
Demo 拿 7 和 2 走了七个运算符:+ 得 9、- 得 5、* 得 14、/ 得 3.5、
// 得 3、% 得 1、幂(pow)得 49。
divmod(7, 2) 一次给出 (3, 1),既省一次除法又省一次取余。做分页、算余数的时候用它。
这七个里,只有 / 的结果是浮点。其余六个在整数输入下都是整数。
这一点决定了后面很多东西——包括为什么不能用 / 去算索引。
二、坑一:真除法永远出浮点
7 除以 2 在数学上是 3.5,/ 就老实给 3.5,哪怕两个数都是整数。
Python 3 里 7 / 2 是 3.5,不是 3。要用整商,必须写 //。
这个坑的典型现场是取数组下标:arr[len(arr) / 2] 会抛 TypeError,因为下标不能是浮点。
写成 arr[len(arr) // 2] 才对。
三、坑二:负数整除向下取整
这条最容易错。// 不是「砍掉小数部分」,是「向负无穷方向取整」。
Demo 打了四组:-7 // -2 得 3 余 -1;-7 // 2 得 -4 余 1;
7 // -2 得 -4 余 -1;7 // 2 得 3 余 1。
如果按「向零截断」,-7 // 2 该是 -3,可它给了 -4。
怎么判断哪个对?Demo 加了一列验算:商 × 除数 + 余数。四行结果全部等于原来的被除数。
这是唯一可靠的判据——算术恒等式不撒谎。
四、坑三:浮点不是精确的
0.1 + 0.2 打出来是 0.30000000000000004,0.1 + 0.2 == 0.3 是 False,差 5.55e-17。
这不是 Python 的问题,是二进制浮点的固有性质:0.1 在二进制里是无限循环小数,存进 64 位必然截断。
两条出路:判等写 abs(x - y) < 1e-9;算钱用整数「分」或者 decimal.Decimal。
Demo 用 Decimal 算了一遍,结果正好是 0.3。
算钱这件事上没有中间路线。用浮点算金额,迟早会差一分钱——而对账系统不认「差不多」。
五、坑四:优先级与结合方向
2 + 3 * 4 得 14,因为 * 先算;(2 + 3) * 4 得 20。这个大家都知道。
容易错的是幂:pow(2, pow(3, 2)) 得 512,不是 64。
因为幂运算是右结合,先算 3 的 2 次方得 9,再算 2 的 9 次方。
1 + 2 > 2 得 True,因为算术比比较先算——先得 3,再判 3 > 2。
这条在多条件判断里很关键,写错一处整个条件语义就变了。
Demo 最后量了一次大整数乘法:2000 次 *= 99991,耗时 1.68 毫秒,结果有 33220 个二进制位、
10000 位十进制。
顺手撞到一个真实限制:Python 3.11 起默认不准把超过 4300 位的整数转成十进制字符串,
Demo 里被拦下了,显式放宽上限才转成功。这是防暴力破解的护栏,不是 bug。
六、动手 Demo:加减乘除之外的四个坑
先用 7 和 2 走一遍七个运算符并试 divmod;再列四组正负组合,用「商 × 除数 + 余数」验算整除方向;
然后对比 0.1 + 0.2 与 Decimal;最后测四个优先级例子,并量一次 2000 步大整数乘法的耗时。
a, b = 7, 2
print(a + b, a - b, a * b, a / b, a // b, a % b, a ** b)
print(divmod(a, b)) # (3, 1)
for x, y in ((-7, -2), (-7, 2), (7, -2), (7, 2)):
print(x // y, x % y, x // y * y + x % y) # 末列恒等于 x
print(repr(0.1 + 0.2)) # 0.30000000000000004
print(2 ** 3 ** 2) # 512,右结合
big = 1
for _ in range(2000):
big *= 99991
实跑输出:
--------------------------------------------------------------------
运算符:加减乘除之外的四个坑
--------------------------------------------------------------------
式子 结果
------------
a + b 9
a - b 5
a * b 14
a / b 3.5
a // b 3
a % b 1
a ** b 49
divmod(a, b) (3, 1)
坑一:真除法永远出浮点。7/2 是 3.5,不是 3。
坑二:负数整除向下取整,不是向零截断。
式子 商 余 商*除数+余
------------------------------
-7 // -2 3 -1 -7
-7 // 2 -4 1 -7
7 // -2 -4 -1 7
7 // 2 3 1 7
最后一列处处等于左边被除数——这是取整方向对不对的唯一判据。
坑三:浮点不是精确的。
0.1 + 0.2 0.30000000000000004
0.1 + 0.2 == 0.3 False
差多少 5.55e-17
Decimal 算一遍 Decimal('0.3')
要判等就写 abs(x - y) < 1e-9;要算钱就用整数分或 Decimal。
坑四:优先级。** 比 * 高,* 比 + 高,+ 比比较符高。
式子 结果 为什么
---------------------------------------
2 + 3 * 4 14 先乘再加,14
(2 + 3) * 4 20 括号先算,20
2 ** 3 ** 2 512 幂是右结合,先 3**2
1 + 2 > 2 True 先加后比
2000 次大整数乘法 1.74 毫秒
结果有多少二进制位 33220 位
转成十进制字符串 被拦下:Exceeds the limit (4300 digits) for integer string conversion
放宽上限再转 10000 位
Python 3.11 起默认不准把超过 4300 位的整数转成十进制字符串,
这是防暴力破解的护栏,不是 bug。真要转就显式放宽。
七个数一行看完:9、5、14、3.5、3、1、49。
只有 / 出浮点,其余六个都是整数——这是选运算符时的第一判据。
负数那组的末列全是 -7、-7、7、7,正好是被除数。能用恒等式验算的结论,就别靠背。
浮点那三行:0.30000000000000004、False、5.55e-17。
接着 Decimal 打出 Decimal('0.3'),干净利落。
pow(2, pow(3, 2)) 是 512。想成 64 的人,是把幂当成了左结合。
大整数那两行:1.68 毫秒、33220 个二进制位。
转十进制时被 4300 位的默认上限拦下,显式放宽后得到 10000 位。
30 秒实验
- 把 7 和 2 换成 -7 和 2,重新对一遍七个运算符,看哪个变了号。
- 用
Decimal算一遍 0.1 + 0.2,再用浮点算一遍,把两个结果存成字符串对比。 - 想一想:为什么幂运算设计成右结合?
常见误区
- 「
/和//差不多」——一个出浮点一个出整数,用错就报TypeError。 - 「
//就是砍掉小数」——负数时方向相反,-7 // 2是 -4。 - 「浮点误差可以忽略」——算钱的时候,差一分钱就过不了账。
自测三问(点开对答案)
1. `7 / 2` 和 `7 // 2` 分别得什么? —— `/` 得 3.5(浮点),`//` 得 3(整数)。 2. `-7 // 2` 是多少?怎么验证? —— -4;用 商 × 除数 + 余数 = -4×2+1 = -7 验算。 3. `pow(2, pow(3, 2))` 为什么是 512? —— 幂运算右结合,先算 3 的 2 次方得 9,再算 2 的 9 次方得 512。算完了,接下来让程序自己判断——条件语句决定走哪条路。
- ☐ (本节完,下一节 3.2.5 使用Python条件语句:对比数的大小)