当前位置: 首页 > 知识库问答 >
问题:

为什么C在使用模运算时输出负数?

太叔弘壮
2023-03-14

数学:

如果你有这样一个等式:

x = 3 mod 7

x可能是-4, 3, 10, 17, ..., 或者更一般地说:

x = 3 + k * 7

其中k可以是任意整数。我不知道模运算是为数学定义的,但因子环肯定是。

蟒蛇:

在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总是返回一个非负值。(另一个用法是欧几里德算法。因为在使用该算法之前,可以使两个数字都为正,所以模的符号很重要。)

附加材料:

请参阅维基百科,了解modeo在不同语言中的作用。

共有3个答案

莫繁
2023-03-14

这个规范的论点是什么?

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

有没有一个地方可以让制定这种标准的人讨论它?

你会在comp上找到有趣的讨论。朗克。缓和,并在较小程度上,comp。朗克

房学文
2023-03-14

回到那时,设计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的内容。(感谢Clear的评论,该评论给了我一个使用Later day 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;
}

真实事实:我游说帕斯卡标准委员会以正确的方式进行mod,直到他们让步。令我惊恐的是,他们用错误的方法做了整数除法。所以它们甚至不匹配。

编辑:Clear给了我一个想法。我正在做一个新的。

#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);
    }
}
狄天逸
2023-03-14

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

整数除法遵循两条规则:

  • 非整数商舍入为零;和
  • 等式红利=商*除数余数由结果满足。

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

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

  • 处理器指令集设计优化了普通大小写(除法),而非普通大小写(模);
  • 一致性(舍入为零,并尊重除法方程)优于数学正确性;
  • C更喜欢效率和简单(尤其是考虑到将C视为“高级汇编器”的趋势);和
  • C更喜欢与C的兼容性
 类似资料:
  • C++ 重载运算符和重载函数 C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。 在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。 下面的实例演示了如何重载提取运算符 >> 和插入运算符 <<。#include <iostream> u

  • 我写了一段代码,对给定的输入进行排序,但在返回排序后的输入后,它将始终返回相同的输出。我正在使用创建控制台应用程序。Visual Studio中的NET 5.0(当前版本)。 当我输入“Car-Apple-Banana”时,它会按单词排序。排序() 之后,我打印出原始输入,但它似乎也被排序。我不知道为什么,因为我从不分类。 当输入为:“汽车苹果香蕉” 我现在得到的输出是: 苹果香蕉车 苹果香蕉车

  • 问题内容: 我读到从Java 7开始,像在第一条语句中那样在右侧指定类型来创建Collections是不好的样式,因为编译器可以从左侧推断类型。 我的问题是,当像这样初始化列表时,编译器找不到类型,并且我收到未经检查的类型警告: 问题答案: 编译器不会 推断 类型,因为您正在实例化 raw 。但是它足够聪明,可以警告您在使用此(原始)对象时可能会出现问题。 值得一提的是此警告背后的原因。由于类型擦

  • 我刚刚开始拿起react.js所以我通过了很多教程,我偶然发现了这一点,基本上是指从状态中删除一个项目。 这就是那个家伙向我介绍删除功能的方式 由于我对javascript不太熟悉,我很难弄清楚< code >是什么...操作员正在做什么,以及他在给定场景中使用它的确切原因。因此,为了更好地理解它是如何工作的,我在控制台上玩了一会儿,我意识到< code>array = [...数组]。但这是真的

  • 问题内容: 何时使用和何时使用运算符? Java提供了两个选项来检查分配兼容性。什么时候使用? 问题答案: 我认为官方文档为您提供了答案(尽管以一种非常具体的方式): 此方法与Java语言instanceof运算符动态等效。 我认为这主要是指在运行时处理类型反射的代码中使用。特别是,我想说它的存在是为了处理您可能不事先知道要检查其成员资格的类的类型的情况(尽管这些情况可能很少)。 例如,您可以使用

  • 这个问题基本上是说迭代一个整数,然后计算这个数是否可以被它的左边数整除,如果它可以整除,则返回一个布尔数组。 73312 第一个数字没有左边的数字,所以它是假的 我运行了测试,一切都很好,但是有了这个数字(73312),当它应该返回false时,它会返回true。 预期输出 实际输出