← 返回题目列表

Python 里 / 和 // 有什么区别?负数取模 -7 % 3 为什么是 2?

中等 第 17 / 22 题 更新于 2026/07/28
Python除法取模divmod

简化版

Python 有两个除法运算符:/ 是「真除法」永远返回浮点数(6 / 32.0 不是 2),// 是「整除/地板除」返回向下取整的结果;而 % 是取模,配合 // 满足 a == (a // b) * b + a % b关键点:① / 真除法——即使能整除也返回 float(4 / 2 == 2.0),这是 Python 3 的行为(Python 2 里 / 对整数是整除,是常见迁移坑);② // 地板除——向「负无穷」方向取整(floor),所以 7 // 2 == 3-7 // 2 == -4(不是 -3!向下取整);③ % 取模——Python 的取模结果符号「跟除数(第二个操作数)走」,所以 -7 % 3 == 2(正)、7 % -3 == -2(负),这和 C/Java(结果跟被除数走,-7 % 3 == -1)不同。-7 % 3 == 2 的原因-7 // 3 == -3(向下取整),则 -7 % 3 = -7 - (-3)*3 = -7 + 9 = 2divmod(a, b) 一次返回 (a // b, a % b)。核心记忆:/ 永远浮点、// 向下取整(负数向更小取)、% 符号跟除数走(-7%3=2),三者满足 a=(a//b)*b + a%b

详细版

三个运算的规则

运算名称规则例子
/真除法永远返回 float6/3 → 2.07/2 → 3.5
//地板除向下(负无穷)取整7//2 → 3-7//2 → -4
%取模结果符号跟除数走-7%3 → 27%-3 → -2
divmod(a,b)商和余数返回 (a//b, a%b)divmod(-7,3) → (-3,2)
print(6 / 3)        # 2.0(真除法,即使整除也是 float)
print(7 / 2)        # 3.5
print(7 // 2)       # 3(向下取整)
print(-7 // 2)      # -4(向负无穷取整,不是 -3!)
print(7 // -2)      # -4
print(7.0 // 2)     # 3.0(有 float 参与,// 返回 float)

# 取模:符号跟除数(第二个操作数)
print(-7 % 3)       # 2  (除数 3 正 → 结果正)
print(7 % -3)       # -2 (除数 -3 负 → 结果负)
print(-7 % -3)      # -1

# 恒等式:a == (a // b) * b + (a % b)
a, b = -7, 3
print((a // b) * b + (a % b))   # -3*3 + 2 = -9+2 = -7 ✓

# divmod 一次拿商和余数
print(divmod(-7, 3))            # (-3, 2)

⚠️ 抓住一条铁律就能推出所有结果:Python 的 // 是「地板除」(向负无穷取整),而 % 由恒等式 a == (a // b) * b + a % b 反推出来——记住 // 向下取整、余数补齐恒等式,-7 % 3 = 2 就是自然结果。很多人被 -7 % 3 == 2 搞懵,是因为 C/Java 的取模「跟被除数走」(-7 % 3 == -1,因为它们的整数除法是「向零截断」)。Python 选择了「向下取整 + 余数符号跟除数」的组合,好处是:取模结果永远和除数同号,对正除数(最常见)永远得到 [0, b) 的非负余数——这对「循环取索引」idx % n、「时钟/角度回绕」(h - 3) % 12、「哈希分桶」等场景极其方便,负数也能得到正确的非负下标,不用额外 +n 修正。理解这个设计意图,就不会觉得它「反直觉」了。

完整版教学

一、真除法 / 与地板除 //

先分清两个除法:

/ (真除法,true division):
  永远返回浮点数 float
  6 / 3 → 2.0(即使能整除,也是 2.0 不是 2)
  7 / 2 → 3.5
  → Python 3 的行为:/ 总是"数学意义的除法",结果带小数

// (地板除,floor division):
  返回"向下取整"的商
  7 // 2 → 3(3.5 向下取 3)
  结果类型:两个 int → int;有 float 参与 → float
  7 // 2 → 3(int)
  7.0 // 2 → 3.0(float,但值是整数)

Python 2 的坑(迁移常见):
  Python 2 里 / 对两个整数是"整除":7 / 2 → 3(不是 3.5)
  Python 3 改成真除法:7 / 2 → 3.5
  → Py2 迁 Py3 时,原来靠 / 做整除的代码会变浮点(要改成 //)
  (from __future__ import division 可在 Py2 启用 Py3 行为)

选择:
  要精确小数结果 → /
  要整数商(如索引、页数、分组)→ //

所以 / 真除法永远返回 float、// 地板除向下取整;Py2 的 / 是整除是迁移坑

先分清两个除法:/(真除法)永远返回浮点数6/3 → 2.0 即使整除也是 2.0、7/2 → 3.5,Python 3 的行为,总是数学意义的除法);//(地板除)返回向下取整的商7//2 → 3,两个 int→int、有 float 参与→float,7.0//2 → 3.0)。Python 2 的坑(迁移常见):Py2 里 / 对两个整数是「整除」(7/2 → 3)、Py3 改成真除法(7/2 → 3.5),迁移时靠 / 做整除的代码会变浮点(要改成 //)。选择:精确小数用 /、整数商(索引/页数/分组)用 //。理解「/ 真除法永远返回 float(即使整除)、// 地板除向下取整(int/int→int,有 float→float);Py2 的/是整除是迁移坑;小数用/、整数商用//」,就掌握了两个除法。

二、地板除的「向下取整」——负数的关键

理解 // 对负数的行为:

// 是"地板除"(floor division):
  向"负无穷"方向取整(数学 floor 函数)
  → 不是"向零截断"(C/Java 那样)

正数没差别:
  7 // 2 = 3(3.5 向下取 3,向零截断也是 3)

负数才见分晓:
  -7 // 2 = ?
    数学结果 -3.5
    向下取整(向负无穷)→ -4  ✓ Python
    向零截断 → -3  ✗(C/Java 的整数除法)
  → Python 的 -7 // 2 = -4(比 -3.5 更小的整数)

  7 // -2 = ?
    数学 -3.5 → 向下取整 → -4

图示(数轴上"向下"= 向左 = 向负无穷):
  -4    -3.5    -3
   |------●------|
   ↓ floor 取左边的 -4

对比截断(trunc,向零):
  trunc(-3.5) = -3(向右,靠近 0)
  floor(-3.5) = -4(向左,远离 0)
  → Python // 用 floor,C/Java 整数除用 trunc

要"向零截断"的整除:
  用 int(a / b) 或 math.trunc(a / b)
  int(-7 / 2) = int(-3.5) = -3(向零)

所以 // 向负无穷取整(floor):-7//2=-4(不是-3);要向零截断用 int(a/b)

// 是「地板除」,向「负无穷」方向取整(数学 floor),不是「向零截断」。正数没差别(7//2=3);负数才见分晓-7//2 数学结果 -3.5、向下取整(向负无穷)→ -4(Python),而向零截断→ -3(C/Java);7//-2 同样是 -4。图示:数轴上「向下=向左=向负无穷」,floor(-3.5) 取左边的 -4。对比截断:trunc(-3.5)=-3(向零)、floor(-3.5)=-4(向负无穷)。要「向零截断」的整除:int(a/b)math.trunc(a/b)int(-7/2)=int(-3.5)=-3)。理解「// 向负无穷取整(floor):-7//2=-4 不是-3(正数无差别、负数见分晓);floor 向左远离 0、trunc 向零;要向零截断用 int(a/b)」,就掌握了地板除的负数行为。

三、取模 % 与恒等式

理解取模和它与地板除的关系:

取模的恒等式(一切的根源):
  a == (a // b) * b + (a % b)
  → 余数 = a - (a // b) * b
  → 因为 // 是地板除,余数由它反推

Python 取模的特点:结果符号"跟除数(b)走"
  -7 % 3:
    -7 // 3 = -3(向下取整)
    -7 % 3 = -7 - (-3)*3 = -7 + 9 = 2  ✓(正,跟除数 3)
  7 % -3:
    7 // -3 = -3(向下取整,2.33→ -3? 不,7/-3=-2.33 向下取 -3)
    7 % -3 = 7 - (-3)*(-3) = 7 - 9 = -2  ✓(负,跟除数 -3)
  -7 % -3:
    -7 // -3 = 2(-7/-3=2.33 向下取 2)
    -7 % -3 = -7 - 2*(-3) = -7 + 6 = -1(负,跟除数)

规律:
  除数 b > 0 → 余数在 [0, b)(非负)
  除数 b < 0 → 余数在 (b, 0](非正)
  → 余数符号总和除数一致

对比 C/Java(取模跟被除数走):
  C: -7 % 3 = -1(因为 C 整数除法向零截断:-7/3=-2,
     -7 - (-2)*3 = -7+6 = -1)
  → 语言设计不同:Python floor+余数跟除数、C trunc+余数跟被除数

所以取模由恒等式 a=(a//b)*b+a%b 推出,Python 余数符号跟除数走

取模的恒等式(一切的根源)a == (a // b) * b + (a % b),余数 = a - (a // b) * b,因为 // 是地板除、余数由它反推。Python 取模的特点:结果符号「跟除数(b)走」-7 % 3——-7//3=-3-7%3 = -7-(-3)*3 = -7+9 = 2(正,跟除数 3);7 % -3 = -2(负,跟除数 -3);-7 % -3 = -1。规律除数 b>0 → 余数在 [0,b) 非负、除数 b<0 → 余数在 (b,0] 非正(余数符号总和除数一致)。对比 C/Java(取模跟被除数走):C 里 -7 % 3 = -1(整数除法向零截断)。理解「取模恒等式 a=(a//b)*b+a%b、余数由地板除反推;Python 余数符号跟除数走(-7%3=2 正、b>0 余数在[0,b));C/Java 跟被除数走(-7%3=-1)」,就掌握了取模。

四、-7 % 3 == 2 的完整推演

-7 % 3 == 2 一步步算清楚:

求 -7 % 3:

第一步:算 -7 // 3(地板除,向下取整)
  -7 / 3 = -2.333...
  向下取整(向负无穷)→ -3
  (注意不是 -2!-2.333 向下是更小的 -3)
  所以 -7 // 3 = -3

第二步:用恒等式 a = (a//b)*b + a%b 求余数
  -7 = (-7 // 3) * 3 + (-7 % 3)
  -7 = (-3) * 3 + r
  -7 = -9 + r
  r = -7 + 9 = 2
  所以 -7 % 3 = 2 ✓

验证符号规律:
  除数 3 > 0 → 余数应在 [0, 3) → 2 ✓(在范围内)

对比 C 语言(会得 -1):
  C 整数除法向零截断:-7 / 3 = -2(截断 -2.333)
  C: -7 % 3 = -7 - (-2)*3 = -7 + 6 = -1
  → 差别全在"除法怎么取整"(floor vs trunc)

一句话推法:
  记 // 向下取整 + 恒等式补余数
  → -7//3=-3 → 余数=−7−(−9)=2

所以-7%3=2:因-7//3=-3(向下取整),恒等式补出余数-7-(-9)=2

-7 % 3 == 2 一步步算清:第一步算 -7 // 3(地板除向下取整)-7/3 = -2.333、向下取整(向负无穷)→ -3(不是 -2,-2.333 向下是更小的 -3);第二步用恒等式求余数-7 = (-3)*3 + r-7 = -9 + rr = 2,所以 -7 % 3 = 2。验证符号规律:除数 3>0 → 余数应在 [0,3) → 2 在范围内。对比 C(会得 -1):C 整数除法向零截断 -7/3=-2-7%3 = -7-(-2)*3 = -1——差别全在「除法怎么取整」(floor vs trunc)。理解「-7%3=2 推演:①-7//3=-3(向下取整,-2.333 向下是-3)②恒等式-7=(-3)*3+r→r=2;C 里向零截断-7/3=-2 得余数-1;差别在 floor vs trunc」,就彻底掌握了这个经典问题。

五、为什么 Python 这样设计——实用价值

理解 floor 取模的设计意图:

Python 选 floor 除法 + 余数跟除数的好处:

核心好处:对正除数,余数永远非负 [0, b)
  → 负数也能得到正确的"非负下标/回绕值"

场景1:循环索引/环形缓冲
  n = 5
  idx = (i - 2) % n   # 无论 i 多小,结果都在 [0,5)
  i=0: (0-2)%5 = -2%5 = 3 ✓(正确回绕到 3)
  → C 里 -2 % 5 = -2(负下标,越界!要手动 +5)

场景2:时钟/角度回绕
  hour = (current - 3) % 12   # 往前 3 小时
  (1 - 3) % 12 = -2 % 12 = 10 ✓(正确)
  → 直接得正确的小时数,不用判断负数

场景3:哈希分桶
  bucket = hash(key) % num_buckets
  # hash 可能为负,Python 的 % 保证 bucket 在 [0, num_buckets)
  → 直接当数组下标,安全

场景4:判断奇偶/整除(正除数时和 C 一致)
  n % 2 == 0   # 偶数(n 正负都对,因为 % 2 结果非负)

对比 C 的痛:
  C 里负数取模得负,做下标/回绕要额外 ((x % n) + n) % n 修正
  → Python 的设计省掉这个修正,更符合"取模用于回绕"的意图

所以 floor 取模好处:正除数余数非负,负数也能直接当下标/回绕值,省+n 修正

Python 选 floor 除法 + 余数跟除数 的好处:核心——对正除数,余数永远非负 [0, b),负数也能得到正确的「非负下标/回绕值」场景1 循环索引/环形缓冲(i-2) % n 无论 i 多小结果都在 [0,n)(-2%5=3 正确回绕,C 里 -2%5=-2 负下标越界要手动 +5);场景2 时钟/角度回绕(current-3) % 12 直接得正确小时数;场景3 哈希分桶hash(key) % num_buckets 保证在 [0,n) 直接当下标;场景4 判奇偶n % 2 == 0(n 正负都对)。对比 C 的痛:C 里负数取模得负、做下标/回绕要 ((x%n)+n)%n 修正,Python 省掉这个修正。理解「floor 取模好处:正除数余数非负[0,b),负数也能直接当下标/回绕值;场景循环索引/时钟回绕/哈希分桶/判奇偶;C 里负取模得负要+n 修正,Python 省掉」,就理解了设计意图。

六、divmod 与实践总结

理解 divmod 和实践要点:

divmod(a, b):一次返回 (商, 余数) = (a // b, a % b)
  divmod(17, 5) → (3, 2)   (17 = 3*5 + 2)
  divmod(-7, 3) → (-3, 2)
  好处:
    ① 一次算出商和余数(比分别 // 和 % 高效,只除一次)
    ② 常用于进制转换、时间换算、分页

  例:秒转 时:分:秒
    m, s = divmod(total_seconds, 60)
    h, m = divmod(m, 60)
    → (h, m, s)

  例:分页
    pages, remainder = divmod(total, per_page)
    if remainder: pages += 1   # 有余数多一页

实践速查:
  精确小数     → /(返回 float)
  整数商       → //(向下取整)
  余数/回绕     → %(符号跟除数)
  商和余数      → divmod
  向零截断整除   → int(a/b) 或 math.trunc
  向上取整      → -(-a // b) 或 math.ceil(a/b)

浮点除法注意:
  0.1 + 0.2 != 0.3(浮点精度),% 对浮点也有精度问题
  → 精确小数用 decimal.Decimal / fractions.Fraction

核心总结:
  / 永远浮点、// 向下取整(负数向更小)、% 符号跟除数
  恒等式 a=(a//b)*b+a%b、-7%3=2、divmod 一次拿商余
  floor 取模让负数能直接做下标/回绕(省 +n 修正)

所以 divmod 一次拿商余(时间/分页常用),按需选 / // % divmod

divmod(a, b) 一次返回 (商, 余数) = (a//b, a%b)divmod(17,5)→(3,2)divmod(-7,3)→(-3,2))。好处:① 一次算出商和余数(只除一次、比分别算高效)、② 常用于进制转换/时间换算/分页(秒转时分秒 m,s=divmod(sec,60); h,m=divmod(m,60);分页 pages,rem=divmod(total,per_page))。实践速查:精确小数用 /、整数商用 //、余数/回绕用 %、商和余数用 divmod、向零截断用 int(a/b)、向上取整用 -(-a//b)math.ceil。浮点注意:0.1+0.2!=0.3(精度)、精确小数用 Decimal/Fraction。理解「divmod 一次拿商余(时间换算/分页常用只除一次);速查小数/、整数商//、回绕%、商余 divmod、向零 int(a/b)、向上-(-a//b);浮点有精度问题用 Decimal」,就掌握了 divmod 与实践。

记忆钩子:「Python 三个除法/取模运算:①/ 真除法永远返回 float(6/3=2.0 即使整除,Py2 里/是整除是迁移坑)②// 地板除向下取整(向负无穷 floor,不是向零截断):7//2=3、★-7//2=-4(不是-3!因-3.5 向下取更小的-4)③% 取模,结果符号跟除数(第二个操作数)走:-7%3=2(除数正→正)、7%-3=-2(除数负→负);根源恒等式 a==(a//b)*b+a%b,余数由地板除反推;★-7%3=2 推演:-7//3=-3(向下取整)→-7=(-3)*3+r→r=2(和 C/Java 不同,它们向零截断得-7%3=-1);Python 这样设计的好处:正除数余数永远非负[0,b),负数也能直接当下标/回绕值(idx%n、时钟(h-3)%12、哈希分桶),省掉 C 里的((x%n)+n)%n 修正;divmod(a,b)一次返回(a//b,a%b),时间换算/分页常用」

七、常见误区与追问

  • 误区:6 / 3 返回整数 2。 在 Python 3 里 / 是真除法、永远返回浮点数,6 / 32.0 而不是 2(即使能整除);要得到整数商用 //6 // 32);注意 Python 2 里 / 对两个整数是整除(6/2 得 3、7/2 得 3),这是 Py2 迁 Py3 的常见坑——原来靠 / 做整除的代码在 Py3 会变成浮点,需要改成 //
  • 误区:-7 // 2 等于 -3(向零截断)。 等于 -4——Python 的 // 是「地板除」,向负无穷方向取整(floor),-7/2 = -3.5 向下取整得更小的 -4,而不是向零截断的 -3;正数没有区别(7//2=3),只有负数参与时 floor 和 trunc 才不同;要「向零截断」的整除用 int(a/b)math.trunc(a/b)
  • 误区:Python 的 -7 % 3 应该等于 -1(像 C/Java 一样)。 Python 里等于 2——因为 Python 的取模结果符号「跟除数走」,除数 3 为正、余数就非负(落在 [0,3)),由恒等式 -7 = (-7//3)*3 + r = (-3)*3 + r 反推出 r=2;C/Java 的取模「跟被除数走」(它们整数除法向零截断),所以得 -1;两者都满足各自的「商×除数+余数=被除数」,只是取整方向不同。
  • 误区:% 只能用于整数。 也能用于浮点数——7.5 % 2 = 1.55.5 % 2.0 = 1.5;不过浮点取模会有浮点精度问题(如 0.1 % 0.03 可能不精确),需要精确结果时用 decimal.Decimalfractions.Fraction;另外 divmod 也支持浮点(divmod(7.5, 2)(3.0, 1.5))。
  • 追问:为什么 -7 % 3 是 2?请推导。 分两步:① 先算 -7 // 3,Python 的 // 是地板除、向负无穷取整,-7/3 = -2.333 向下取整得 -3(不是 -2);② 再用恒等式 a = (a // b) * b + (a % b) 反推余数:-7 = (-3) * 3 + r,即 -7 = -9 + r,解出 r = 2;所以 -7 % 3 = 2;验证符号规律:除数 3 为正,余数落在 [0, 3) 内,2 符合;对比 C 语言因整数除法向零截断(-7/3=-2),得 -7 % 3 = -7 - (-2)*3 = -1
  • 追问:Python 的取模为什么设计成「余数符号跟除数走」,有什么实际好处? 好处是「对正除数,余数永远非负、落在 [0, b) 区间」,这让负数也能得到正确的「非负下标/回绕值」,非常适合取模的常见用途——环形缓冲/循环索引 (i-k) % n(i 再小结果也在 [0,n) 内、能正确回绕)、时钟/角度回绕 (hour-3) % 12、哈希分桶 hash(key) % num_buckets(hash 为负时结果仍非负、可直接当数组下标);而 C/Java 里负数取模得负数,做这些回绕/下标运算要额外写 ((x % n) + n) % n 来修正,Python 的设计省掉了这个修正、更贴合「取模=回绕」的语义意图。
  • 追问:divmod 有什么用,比分别写 // 和 % 好在哪? divmod(a, b) 一次返回 (a // b, a % b) 元组,好处是:① 只做一次除法运算就同时得到商和余数(比分别写 a // ba % b 少算一次,效率略高,尤其大整数时明显);② 语义清晰、代码简洁,特别适合「进制转换、时间换算、分页」这类同时需要商和余数的场景,比如秒转时分秒 m, s = divmod(total, 60); h, m = divmod(m, 60)、分页 pages, rem = divmod(total, per_page)(有余数则页数 +1);结果同样遵循地板除规则,divmod(-7, 3)(-3, 2)

八、加强记忆

Python 有两个除法运算符加一个取模,全由「地板除 + 恒等式」推出/(真除法)永远返回浮点数6/3 = 2.0 即使整除也是 float;Python 2 里 / 对整数是整除、是迁移坑);//(地板除)向「负无穷」方向取整(floor,不是向零截断)7//2 = 3-7//2 = -4(不是 -3!-3.5 向下取更小的 -4)%(取模)结果符号「跟除数走」-7 % 3 = 2(除数正→正)、7 % -3 = -2(除数负→负),这和 C/Java(跟被除数走、-7%3=-1)不同。根源是恒等式 a == (a // b) * b + (a % b)——余数由地板除反推。-7 % 3 = 2 推演-7//3 = -3(向下取整)→ -7 = (-3)*3 + rr = 2Python 这样设计的好处:对正除数余数永远非负 [0, b),负数也能直接当下标/回绕值(循环索引 idx % n、时钟 (h-3) % 12、哈希分桶),省掉 C 里的 ((x%n)+n)%n 修正。divmod(a, b) 一次返回 (a//b, a%b)(时间换算、分页常用、只除一次)。一句话「**/ 永远 float、// 向下取整(负数向更小:-7//2=-4)、% 符号跟除数(-7%3=2);恒等式 a=(a//b)b+a%b;floor 取模让负数直接做下标/回绕(省+n 修正);divmod 一次拿商和余数*」。