不能对零取模?

67

为什么X % 0是无效表达式?

我一直认为X % 0应该等于X。既然你不能除以零,那么答案自然应该是余数X(剩下的所有东西),但为什么这个表达式是无效的呢?


1
@Mu,这些回答有很大的不同。在这里,Petar解释了%符号的数学定义,而在那里,他们解释了为什么错误不够清晰明了。 - xanatos
1
Knuth等人在《具体数学》(Concrete Mathematics)中给出了与你相同的定义。(PDF文档,第82页,定义3.22。) - Csq
1
你不能在没有除法的情况下得到余数。 - JohnJohn
2
这里的概念化和推理非常差。由于除以0得到的商是未定义的,所以余数也必须是未定义的。而且“总是认为”某些事情很奇怪,因为你肯定从未被教过或告知过。 - Jim Balter
1
"响应非常不同" - 这与问题是否重复无关。唉。 - Jim Balter
显示剩余2条评论
9个回答

50

C++标准(2003年版)第5.6/4节中指出:

[...] 如果除法或取模运算符的第二个操作数为零,则其行为是未定义的; [...]

也就是说,下面这些表达式会导致未定义行为(UB):

X / 0; //UB
X % 0; //UB
还要注意,-5 % 2并不等于-(5 % 2)(正如Petar在他的答案评论中所暗示的那样)。这是由实现定义的。规范说明了(§5.6/4):

[...] 如果两个操作数都是非负的,则余数为非负的;如果不是,则余数的符号由实现定义。


我可能会倾向于“部分实现定义”,符号是实现定义的,但一旦选择了符号,值不就固定了吗?但这只是挑刺而已。 - mu is too short
1
程序是否可能因为模零而崩溃,或者只是结果未知? - Zaffy
1
@Zaffy:由于模零会引发未定义行为(UB),因此是有可能使用模零使程序崩溃的,但不保证一定会崩溃。程序崩溃只是 UB 百万种可能性之一。 - Nawaz
1
这被链接为重复项,我认为这篇文章应该更新:对于所有定义了商的aba/b + a%b不再是实现定义,而是等于a(否则行为未定义)。这个变化发生在C99和C++11中(也许在C++03中已经有了TR1,我不知道)。如果您能将问题标记为C,那就太好了,因为在这方面它们是相同的(这是一个C问题,重复了这个问题)。 - mafso
如果你想知道为什么C99和C11不同(不确定C ++),我记得这是C99中的一个缺陷:INT_MIN%-1被定义,尽管在许多平台上会引发异常。在C11中,只有当x / y被定义时,才定义x%y,并且从来不能安全地假定INT_MIN%-1会被评估。 - mafso
我的程序默默地崩溃了,没有任何错误信息,这就是问题所在。 - Masood Lapeh

14

这个答案不适用于数学家。为了提供动机(代价是数学精度),本答案试图解释原理。

数学家:请点击此处。

程序员:请记住,除以0未定义的。因此,依赖除法的 mod 也是 未定义的


这表示正数XD的除法;它由整数部分和小数部分组成:

(X / D) =   integer    +  fraction
        = floor(X / D) + (X % D) / D

重新排列后,你得到:

(X % D) = D * (X / D) - D * floor(X / D)

D 替换为 0

(X % 0) = 0 * (X / 0) - 0 * floor(X / 0)

由于除以 0undefined,因此:

(X % 0) = 0 * undefined - 0 * floor(undefined)
        = undefined - undefined
        = undefined

为什么不能在第二个方程式中将外部D替换为0,使其变成(X % 0) = 0 * (w/e),然后称之为零呢? - Yatharth Agarwal
@YatharthAgarwal 因为 0 * (w/e) 并不总是等于 0。如果 w/e 是一个实数(包括整数),那么它就是 0。如果不是,普通的乘法就无法给出答案,即答案是未定义的。 - Dominick Pastore

6

X % D 的定义是一个数字 0 <= R < D,使得存在一个 Q 满足下列条件:

X = D*Q + R

因此,如果D = 0,则不存在这样的数字(因为0 <= R < 0


2
这并不是真的,据我所知,如果 x < 0,那么 x % y 的符号是由实现定义的。在我的系统上,-5 % 2 恰好是 -1。 - mu is too short
D = 0 时,X = D*Q + R 对于 任何 Q 都有效,其中 X = R 是 OP 所需的。但是,无法满足 0 <= R < 0。您的答案似乎暗示了相反的情况,尽管我可能只是读错了。 - hammar
2
@Petar:不对。-5 % 2实际上并不等于-(5 % 2)。这是有实现定义的。规范中说,如果两个操作数都是非负数,则余数为非负数;否则,余数的符号由实现定义 - Nawaz
1
以上内容适用于数学模数。但是CPU和C编译器实现通常返回与X相同符号的R,因此-5%2 = -(5%2)是正确的。另一方面,Python将返回“真正的”数学模数,因此上述内容将不再正确。https://dev59.com/dXI-5IYBdhLWcg3wVWzv - phuclv

2

我认为因为要获得X % 0的余数,你需要先计算X / 0,这将产生无穷大,而尝试计算无穷大的余数并不是真正可能的。

然而,与您的想法相符的最佳解决方案是这样做:

REMAIN = Y ? X % Y : X

2

另一个可能在概念上易于理解这个问题的方法:

暂且不考虑参数符号的问题,a % b 可以很容易地重写为 a - ((a / b) * b)。如果 b 为零,则表达式 a / b 未定义,因此在这种情况下整个表达式也必须是未定义的。

最终,模运算实际上是一种除法运算,因此如果 a / b 未定义,则合理地期望 a % b 也是未定义的。


1

X % Y 的结果位于整数范围[ 0, Y )内。而X % 0的结果必须大于或等于零,并且小于零。


2
不是这样的,据我所知,如果x < 0,则x%y的符号是实现定义的。在我的系统上,-5%2恰好为-1。 - mu is too short
真的,你知道模数的怪癖。但不幸的是,这足以说明为什么不能用零进行取模。 - K-ballo

0
计算机如何进行除法运算:
从被除数开始,不断减去除数,直到差小于除数为止。你重复减去的次数就是商,剩下的就是余数。例如,将10除以3:
10 - 3 = 7
7 - 3 = 4
4 - 3 = 1

所以

10 / 3 = 3
10 % 3 = 1

将1除以0:

1 / 0
1 - 0 = 1  
1 - 0 = 1  
1 - 0 = 1  
...

所以

1 / 0 = Infinity (technically even infinity is too small, but it's easy to classify it as that)
1 % 0 = NaN

如果没有阻止它的东西,CPU将继续执行此操作,直到超载并返回完全随机的结果。因此,在CPU级别上有一条指令,如果除数为0,则返回 NaN Infinity (取决于您的平台)。

这永远不会结束,所以余数未定义(对于计算机来说是 NaN )。


模运算只能用于整数,那你为什么要谈论浮点数呢? - Nikita Demodov
@NikitaDemodov 我在哪里谈论过浮点数? - Anonymous
当你说1 / 0 = Infinity1 % 0 = NaN时,整数没有infNaN值。它们仅适用于IEEE754标准的浮点数。1.0 / 0.0inf,但1 / 0(如果两个都是整数)会导致崩溃。 - Nikita Demodov
@NikitaDemodov 浮点数的等价值为InfinityNaN,如果是整数,则无法将其转换为正确的类型,但概念相同。 - Anonymous

0

你可以通过使用浮点数的身份mod(a,b)来避免(A%B)的"除以0"情况,其中float(B)=b=0.0是未定义的,或者在任何两个实现之间定义不同,以避免逻辑错误(硬崩溃)而产生算术错误...

通过计算mod([a*b],[b])==b*(a-floor(a))
而不是
计算mod([a],[b])

其中[a*b]==您的x轴,随时间变化 [b] == 秋千曲线的最大值(永远不会达到)== 秋千函数的一阶导数

https://www.shadertoy.com/view/MslfW8


0
我猜是因为要获得X % 0的余数,需要首先计算X / 0,这会产生无穷大,而尝试计算无穷大的余数实际上是不可能的。然而,与您的想法最符合的解决方案是这样做:
ans = Y ? X % Y : X

此外,在C++文档中写道,X % 0或X / 0的结果是未定义的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接