为啥 C++ 使用模数时输出负数?
Posted
技术标签:
【中文标题】为啥 C++ 使用模数时输出负数?【英文标题】:Why does C++ output negative numbers when using modulo?为什么 C++ 使用模数时输出负数? 【发布时间】:2012-07-22 17:44:05 【问题描述】:数学:
如果你有这样的等式:
x = 3 mod 7
x 可以是 ... -4、3、10、17、...,或更一般地说:
x = 3 + k * 7
其中 k 可以是任何整数。我不知道为数学定义了模运算,但因子环肯定是。
Python:
在 Python 中,当您将 %
与正数 m
一起使用时,您将始终得到非负值:
#!/usr/bin/python
# -*- coding: utf-8 -*-
m = 7
for i in xrange(-8, 10 + 1):
print(i % 7)
结果:
6 0 1 2 3 4 5 6 0 1 2 3 4 5 6 0 1 2 3
C++:
#include <iostream>
using namespace std;
int main()
int m = 7;
for(int i=-8; i <= 10; i++)
cout << (i % m) << endl;
return 0;
将输出:
-1 0 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 0 1 2 3
ISO/IEC 14882:2003(E) - 5.6 乘法运算符:
二元 / 运算符产生商,二元 % 运算符 产生第一个表达式除以的余数 第二。如果 / 或 % 的第二个操作数为零,则行为是 不明确的;否则 (a/b)*b + a%b 等于 a。如果两个操作数都是 非负,则余数为非负;如果不是,的符号 余数是实现定义的 74).
和
74) 根据正在进行的 ISO C 修订工作, 整数除法的首选算法遵循定义的规则 ISO Fortran 标准 ISO/IEC 1539:1991,其中商为 总是向零舍入。
来源:ISO/IEC 14882:2003(E)
(我找不到ISO/IEC 1539:1991
的免费版本。有人知道从哪里得到它吗?)
操作好像是这样定义的:
问题:
这样定义有意义吗?
这个规范的论据是什么?创建此类标准的人是否有讨论它的地方?我在哪里可以了解他们决定这样做的原因?
大多数时候,当我使用模数时,我想访问数据结构的元素。在这种情况下,我必须确保 mod 返回一个非负值。因此,对于这种情况,最好 mod 总是返回一个非负值。 (另一种用法是Euclidean algorithm。由于您可以在使用此算法之前将两个数字都设为正数,因此模数的符号很重要。)
附加材料:
请参阅Wikipedia,了解模数在不同语言中的作用。
【问题讨论】:
C(以及 C++)的通常原因是现有硬件以某种方式进行数学运算。语言标准只是记录正在发生的事情(以及没有发生的事情)。 对这个问题的一个有用补充可能是“在 C++ 代码中获得 Python 显示的行为的一个好的替代方法是什么?” 这里解释了获得mod
正值的好方法:[***.com/a/12277233/228965]
【参考方案1】:
在 x86(和其他处理器架构)上,整数除法和取模由单个操作 idiv
(div
用于无符号值)执行,该操作同时产生商和余数(对于字大小的参数,在AX
和 DX
分别)。这个用在C库函数divmod
中,可以被编译器优化为单条指令!
整数除法遵循两个规则:
非整数商向零舍入;和 结果满足方程dividend = quotient*divisor + remainder
。
因此,当一个负数除以一个正数时,商将为负数(或零)。
因此,这种行为可以看作是一系列本地决策的结果:
处理器指令集设计针对常见情况(除法)而不是不太常见情况(取模)进行了优化; 一致性(向零舍入,并遵守除法方程)优于数学正确性; C 更喜欢效率和简单(特别是考虑到将 C 视为“高级汇编程序”的趋势);和 C++ 更喜欢与 C 兼容。【讨论】:
我想知道截断除法比取整除法快的频率,因为二的幂除数很常见,适用于缩放乘法的除数也是如此。【参考方案2】:过去,设计 x86 指令集的人认为将整数除法舍入为零而不是向下舍入是正确且好的。 (愿一千只骆驼的跳蚤在他母亲的胡须里筑巢。)为了保持某种数学正确性,读作“余数”的操作员 REM 必须做出相应的行为。请勿阅读:https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzatk/REM.htm
我警告过你。后来有人做 C 规范决定它会符合编译器以正确的方式或 x86 方式来做。然后一个制定 C++ 规范的委员会决定以 C 的方式来做。后来,在这个问题发布后,一个 C++ 委员会决定以错误的方式进行标准化。现在我们被它困住了。许多程序员编写了以下函数或类似的东西。我可能已经做了至少十几次了。
inline int mod(int a, int b) int ret = a%b; return ret>=0? ret: ret+b;
你的效率很高。
这些天来,我基本上使用以下内容,并加入了一些 type_traits 的东西。(感谢 Clearer 的评论让我想到了使用后来的 C++ 进行改进的想法。见下文。)
<strike>template<class T>
inline T mod(T a, T b)
assert(b > 0);
T ret = a%b;
return (ret>=0)?(ret):(ret+b);
</strike>
template<>
inline unsigned mod(unsigned a, unsigned b)
assert(b > 0);
return a % b;
真实的事实:我游说 Pascal 标准委员会以正确的方式做 mod,直到他们心软。令我恐惧的是,他们以错误的方式进行整数除法。所以他们甚至不匹配。
编辑:Clearer 给了我一个想法。我正在开发一个新的。
#include <type_traits>
template<class T1, class T2>
inline T1 mod(T1 a, T2 b)
assert(b > 0);
T1 ret = a % b;
if constexpr ( std::is_unsigned_v<T1>)
return ret;
else
return (ret >= 0) ? (ret) : (ret + b);
【讨论】:
不会断言a % b >= 0
是正确的吗?一些平台可能会定义a % b
,做正确的事情(偶然地,故意***的延伸)并断言a % b >= 0
。
@Clearer - 不,但你给了我一个好主意。请参阅编辑后的答案。
我让你的内联函数工作了,但是我的编译器给出了一个错误:expected primary-expression before ‘constexpr’。我正在使用带有 -std=c++14 的 GCC。
@tyebillion "if constexpr" 是 C++17 的东西。
@Clearer - 断言说“这个函数不是为处理负 b 而设计的”。如果要让b
为负数,则需要更复杂的解决方案。 [通常b
被认为是积极的,只是a
有时有讨厌的消极倾向]【参考方案3】:
这个规范的论据是什么?
C++ 的设计目标之一是有效地映射到硬件。如果底层硬件以产生负余数的方式实现除法,那么这就是你在 C++ 中使用%
时会得到的结果。这就是它的全部内容。
创建此类标准的人是否有讨论它的地方?
您会发现关于 comp.lang.c++.moderated 的有趣讨论,以及在较小程度上的 comp.lang.c++
【讨论】:
这与 C++ 的“你不用为你不使用的东西付费”的目标非常吻合。为了方便起见,默认情况下不会牺牲性能。如果您需要检查/abs
您的模结果,您可以轻松地将其包装在您需要该行为的任何位置。
如果 x 或 y 为负数且模数非零,编译器可以任意返回正数或负数,是否可以更好地指定“有效映射到硬件”的目标?结果? (x%123456789) 与正数一起正常工作的最快实现可能会产生负数的负结果,但 (x%8) 的最快实现会产生正数。如果 y 为正且 x 可能为负,计算 (x mod y) 的最快方法可能是:m=x%y; if (m<0) m+=y;
,即使编译器...
...对于不能被 y 整除的负 x 值,随机返回正或负结果。我能看到的唯一一件事是通过在 /
上指定 truncate-to-zero 以及在 %
上指定相应的行为来完成,就是使像 x/=4;
或 y%=4;
这样的操作慢三倍于他们原本需要的速度成为。您是否见过任何真正受益于 -5%2=-1 的代码?
@Preet - 我总是希望模数以正确的方式工作,所以我总是为此付费。以上是关于为啥 C++ 使用模数时输出负数?的主要内容,如果未能解决你的问题,请参考以下文章
为啥当我在 MD5 哈希中转换相同的 C++ 字符串时,每次都会获得不同的输出?
当用户输入除整数以外的任何内容时,为啥我的程序会无限循环输出? C++ [重复]
c++里边string类用数组的形式输入为啥无法用cout输出?