为什么C ++在使用modulo时会输出负数?

时间:2012-07-24 11:54:53

标签: c++ standards modulo

数学

如果您有这样的等式:

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的免费版本。有人知道从哪里获取它吗?)

操作似乎是这样定义的:

enter image description here

问题

这样定义它是否有意义?

此规范的参数是什么?是否存在创建此类标准的人员讨论它的地方?我可以在哪里读到他们决定这样做的原因吗?

大多数时候,当我使用modulo时,我想访问数据结构的元素。在这种情况下,我必须确保mod返回一个非负值。因此,对于这种情况,mod总是会返回一个非负值。 (另一种用法是Euclidean algorithm。在使用此算法之前,你可以使两个数字都为正数,模数的符号就很重要。)

其他材料

有关模数在不同语言中的作用的详细列表,请参阅Wikipedia

3 个答案:

答案 0 :(得分:23)

在x86(和其他处理器体系结构)上,整数除法和模数由单个操作执行,idivdiv表示无符号值),它产生商和余数(对于字 - 大小的参数,分别在AXDX中。这在C库函数divmod中使用,可以由编译器优化为单个指令!

整数部门尊重两条规则:

  • 非整数商舍入为零;和
  • 结果满足等式dividend = quotient*divisor + remainder

因此,当将负数除以正数时,商将为负数(或零)。

因此,这种行为可以看作是一系列地方决策的结果:

  • 处理器指令集设计针对不太常见的情况(模数)优化常见情况(除法);
  • 一致性(向零舍入,尊重除法方程)优于数学正确性;
  • C更喜欢效率和简单(特别是考虑到将C视为“高级汇编程序”的倾向);和
  • C ++更喜欢与C兼容。

答案 1 :(得分:10)

  

此规范的参数是什么?

C ++的设计目标之一是有效地映射到硬件。如果底层硬件以产生负余数的方式实现除法,那么如果在C ++中使用%,那就是你所得到的。这就是真的。

  

创建此类标准的人是否有讨论这个问题的地方?

你会发现有关comp.lang.c ++。moderated以及在较小程度上comp.lang.c ++

的有趣讨论。

答案 2 :(得分:6)

在当天,设计x86指令集的人认为将整数除法舍入为零而不是向下舍入是正确和好的。 (可能有一千只骆驼的跳蚤在他母亲的胡须里筑巢。)为了保持一些数学正确性,操作员REM,发音为“#34;余下的&#34;”,必须相应地表现。请勿阅读: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标准委员会以正确的方式进行调整,直到他们妥协。令我恐惧的是,他们以错误的方式进行整数除法。所以他们甚至不匹配。

编辑:清洁工给了我一个主意。我正在研究一个新的。

#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);
    }
}