为啥 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(和其他处理器架构)上,整数除法和取模由单个操作 idivdiv 用于无符号值)执行,该操作同时产生商和余数(对于字大小的参数,在AXDX 分别)。这个用在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 &gt;= 0 是正确的吗?一些平台可能会定义a % b,做正确的事情(偶然地,故意***的延伸)并断言a % b &gt;= 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&lt;0) m+=y;,即使编译器... ...对于不能被 y 整除的负 x 值,随机返回正或负结果。我能看到的唯一一件事是通过在 / 上指定 truncate-to-zero 以及在 % 上指定相应的行为来完成,就是使像 x/=4;y%=4; 这样的操作慢三倍于他们原本需要的速度成为。您是否见过任何真正受益于 -5%2=-1 的代码? @Preet - 我总是希望模数以正确的方式工作,所以我总是为此付费。

以上是关于为啥 C++ 使用模数时输出负数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当我在 MD5 哈希中转换相同的 C++ 字符串时,每次都会获得不同的输出?

当用户输入除整数以外的任何内容时,为啥我的程序会无限循环输出? C++ [重复]

c++里边string类用数组的形式输入为啥无法用cout输出?

在计算机中,为啥负数要用补码的形式表示呢?(图文并茂版)

python做BP神经网络,进行数据预测,训练的输入和输出值都存在负数,为啥预测值永远为正数?

用 c++ 输出,为啥?