-7 / 2 在 C 里是 -3,在 Python 里是 -4,谁算错了?

参考:Andrew Koenig《C 陷阱与缺陷(第二版)》7.7节

目录

三条性质

三条不可能同时成立

[C 到底保证了什么](#C 到底保证了什么)

例子:哈希表下标

[实测:C 给的是哪一套](#实测:C 给的是哪一套)

[同一个式子,Python 给的是相反的答案](#同一个式子,Python 给的是相反的答案)


Koenig 在 7.7 节一上来没写实现,先问了一个问题:

「假定我们让 a 除以 b,商为 q,余数为 r。......我们希望 a、b、q、r 之间维持怎样的关系呢?」

然后列了三条。


三条性质

q*b + r == a ------「最重要的一点......因为这是定义余数的关系」

这条好理解,这就是关于余数的反运算

改变 a 的正负号,q 的符号跟着变,绝对值不变

这是什么意思呢?比如:

q = 5 / 2 = 2;

r = 5 % 2 = 1;

此时改变被除数的符号: 5 -> -5 就有:

q = -5 / 2 = -2;

r = -5 % 2 = -1;

此时也满足第一条:

(-2)*2 + (-1) = -5;

但这样就会与第三条冲突:

b>0 时,保证 r>=0 且 r<b ------「如果余数用于哈希表的索引,确保它是一个有效的索引值很重要」

还是以 -5 举例,第三条应该这么运算:

q = -5 / 2 = -3;

r = -5 % 2 = 1;

这样也满足第一条:

(-3)*2 + 1 = -5;

但这样算与第二条就产生冲突了

第 3 条不是凑数的。做哈希表下标,余数必须是正数------这一点后面会用上。


三条不可能同时成立

书用 3/2 推导的:

  • 3/2:商 1,余 1 ------ 性质 1 满足,没问题。
  • (-3)/2 呢?

    • 先满足性质 2 → 商 -1,那余数必须是 -1 → 性质 3 破了(余数是负的)。
    • 先满足性质 3 → 余数 1 → 由性质 1 反推商是 -2 → 性质 2 破了。

两条路,必破一条。

书里的话:

因此,C 语言或者其他语言在实现整数除法截断运算时,必须放弃上述三条原则中的至少一条。大多数程序设计语言选择了放弃第 3 条,而改为要求余数与被除数的正负号相同。

注意这句:「大多数程序设计语言选择了放弃第 3 条」。有 "大多数",就说明有例外。


C 到底保证了什么

然而,C 语言的定义只保证了性质 1 ,以及当 a>=0 且 b>0 时,保证 |r|<b 以及 r>=0。

后面部分的保证与性质 2 或者性质 3 比较起来,限制性要弱得多。

性质 1 是标准钉死的;性质 2 和性质 3 是"编译器实际上这么干",不是"标准要求这么干"。

(补一条后来发生的事:C99(1999 年)起,标准明文规定整数除法向零截断------"代数商去掉小数部分"。性质 2 从此由标准兜底。但性质 3 到今天也没人保证。)


例子:哈希表下标

书设了一个场景:n 是标识符算出来的结果,要通过除法得到哈希表项 h,满足 0<=h<HASHSIZE。

如果已知 n 恒为非负,一行就够:

cpp 复制代码
h = n % HASHSIZE;

如果 n 可能为负:

cpp 复制代码
h = n % HASHSIZE;
if (h < 0)
    h += HASHSIZE;

书最后还给了一条更省事的建议:「更好的做法是,程序在设计时就应避免 n 的值为负这样的情形,并且声明 n 为无符号数。」

到这里书讲完了。总结就是:C 放弃了"余数永远为正"这条,改用"余数跟被除数同号"。


实测:C 给的是哪一套

cpp 复制代码
#include <stdio.h>

int main(void)
{
    printf("-7 /  2 = %d\n", -7 / 2);
    printf("-7 %%  2 = %d\n", -7 % 2);
    printf(" 7 / -2 = %d\n", 7 / -2);
    printf(" 7 %% -2 = %d\n", 7 % -2);
    printf("-7 / -2 = %d\n", -7 / -2);
    printf("-7 %% -2 = %d\n", -7 % -2);
    return 0;
}
cpp 复制代码
-7 /  2 = -3
-7 %  2 = -1
 7 / -2 = -3
 7 % -2 = 1
-7 / -2 = 3
-7 % -2 = -1

两条规律,跟书对得上:

商向零截断:

-7/2 给 -3,不是 -4。这就是性质 2------符号变了,绝对值没变。

余数跟被除数同号

:-7 % 2 = -1(跟 -7 的负号)、7 % -2 = 1(跟 7 的正号)。这就是书说的"大多数 C 编译器在实践中的做法"。


同一个式子,Python 给的是相反的答案

把同样几组数丢给 Python:

(Python 的 / 是浮点除,所以对照要用整除 //)

|-----------|---------|---------------|---------|--------------|
| 算式 | C 的 / | Python 的 // | C 的 % | Python 的 % |
| -7 ÷ 2 | -3 | -4 | -1 | 1 |
| 7 ÷ -2 | -3 | -4 | 1 | -1 |
| -7 ÷ -2 | 3 | 3 | -1 | -1 |

前两行全部不一样,而且余数的符号正好相反。

规律也清楚了:

  • C :余数的符号跟被除数
  • Python :余数的符号跟除数 (7 % -2 给 -1,跟的是 -2)

回到第一章那三条性质------C 挑的是性质 2,Python 挑的是性质 3。

书说"大多数程序设计语言选择了放弃第 3 条",Python 就是那个没放弃的。

所以谁算错了?都没错。

标准没规定的地方,两种选择都合法。

书那句"大多数 C 编译器在实践中也都是这样做的"------"实践中"三个字,就是在说标准不管这事。


相关推荐
Young Doro2 小时前
独立性检测学习任务
人工智能·学习·数据挖掘
hetao17338372 小时前
2026-10-22~23 hetao1733837 的刷题记录
c++·算法
王老师青少年编程3 小时前
2025年6月GESP真题及题解(C++三级): 选择题和判断题(题解)
c++·真题·gesp·答案·csp·三级·信奥赛
卡卡~~4 小时前
“101计划”核心课程实践教材《数据实验结构指导-C++语言版》题目集
开发语言·c++·学习·算法
lengxuenong5 小时前
树状数组 · 信息学学习手册1-求和3个模板
c++·学习·算法·青少年编程
阿松爱学习5 小时前
【Unity开发】Unity长截图功能实现
unity·c#·游戏引擎
lanxiaoqing025 小时前
演讲训练的第一步:先敢说,再会说
人工智能·学习·学习方法·口才
ShineWinsu6 小时前
对于Qt:信号与槽的解析
c++·qt·gui·lambda·信号·信号与槽·槽函数
WL学习笔记6 小时前
使用string类
c++·后端·string类
xxxiugou1236 小时前
链表算法题精讲:从两数相加到K组翻转
数据结构·c++·算法·链表