负数模运算


191

在C程序中,我正在尝试以下操作(只需检查行为)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

给我的输出与(2, -2 , -2)gcc中的一样。我每次都期待一个积极的结果。模数可以为负吗?有人可以解释这种行为吗?



Answers:


167

C99 要求何时a/b可表示:

(a/b) * b + a%b等于a

从逻辑上讲,这是有道理的。对?

让我们看看这会导致什么:


示例A. 5/(-3)-1

=> (-1) * (-3) + 5%(-3) =5

如果5%(-3)为2,则只会发生这种情况。


示例B. (-5)/3-1

=> (-1) * 3 + (-5)%3 =-5

如果这只能发生(-5)%3-2


1
编译器是否应该足够聪明,并且检测到一个无符号对另一个无符号取模始终为正?当前(好在GCC 5.2),编译器似乎认为在这种情况下,即使两个操作数都为uint32_t或更大,“%”都将返回“ int”,而不是“ unsigned”。
Frederick Nord

@FrederickNord您是否有显示该行为的示例?
chux-恢复莫妮卡

10
了解您所描述的是mod的通常int(a / b)(截断)描述。但是规则也可能是floor(a / b)(Knuth)。在Knuth情况下-5/3-2并且mod变为1。简而言之:一个模块的符号跟随除数符号(截断),另一个模块的符号跟随除数符号(Knuth)。
以撒

1
这是C标准完全不是我想要的情况。我从未想过将截断取零或为负模数,但我经常想截取相反的数,并且需要解决C问题
Joe

142

%C中的运算符不是运算符,而是余数运算符。

模和余数运算符在负值方面有所不同。

对于余数运算符,结果的符号与除数的符号相同,而对于模运算符,结果的符号与除数相同。

C将%操作定义a % b为:

  a == (a / b * b) + a % b

/该整数除法与向截断0。这就是朝着0(而不是朝着负无穷大)进行的截断,将其定义%为余数运算符而不是取模运算符。


8
根据定义,余数是取模运算的结果。不应存在​​余数运算符,因为不存在余数运算,这称为模。
gronostaj

41
@gronostaj不在CS中。查看诸如Haskell或Scheme的高级语言,它们都定义了两个不同的运算符(remainder并且modulo在Scheme remmodHaskell中)。这些运算符的规范在这些语言的划分方式上有所不同:截断为0或负无穷大。C标准从来没有调用方式%模运算,他们只是它命名的 %操作
ouah 2015年

2
不与混淆remainder 功能在C,其与在分割圆入,向最近的语义实现IEEE其余
埃里克

67

基于C99规范: a == (a / b) * b + a % b

我们可以写一个函数来计算(a % b) == a - (a / b) * b

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

对于模运算,我们可以具有以下功能(假设b > 0

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

我的结论是,a % bC语言中的运算是余数运算,而不是模运算。


3
b为负数时,它不会给出正结果(实际上,对于rb均为负数,其结果都小于-b)。为了确保您可以使用所有输入的正结果r + abs(b)或匹配bs符号,可以将条件r*b < 0改为。
马丁·恩德

r + abs(b)当@MartinEnder 为UB时b == INT_MIN
chux-恢复莫妮卡

60

我认为没有必要检查该数字是否为负。

查找正模的简单函数是:

编辑:假设N > 0N + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

这对于x的正值和负值都适用。

原始PS:也由@chux指出,如果您的x和N分别达到INT_MAX-1和INT_MAX之类的值,则只需替换intlong long int

并且,如果它们也跨越了很长的界限(即LLONG_MAX附近),则您应按照此处其他答案所述分别处理正面和负面情况。


1
请注意,当时N < 0,结果可能为modulo(7, -3) --> -2。也x % N + N可能溢出int数学,这是未定义的行为。例如modulo(INT_MAX - 1,INT_MAX)可能导致-3。
chux-恢复莫妮卡

是的,在那种情况下,您可以简单地使用long long int,或单独处理否定情况(以失去简单性为代价)。
Udayraj Deshmukh

9

C99或更高版本中已经解释了其他答案,涉及负操作数的整数除法总是截断为零

注意,在C89中,结果是向上还是向下舍入是实现定义的。因为在所有标准中都是(a/b) * b + a%b相等的a,所以%在C89中,实现负数的结果也是实现定义的。


5

模数可以为负吗?

%可以是负数,因为它是余数运算符,除法后的余数,而不是Euclidean_division之后的余数。由于C99,结果可能为0,负数或正数。

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

所需的运算OP是经典的欧几里德模,不是%

我每次都期待一个积极的结果。

要执行无论何时a/b定义a,b都定义良好的欧几里德模,它具有任何符号,并且结果永远不会为负:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

模运算的结果取决于分子的符号,因此对于yz,您得到-2

这是参考

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html

整数部

本节介绍执行整数除法的功能。这些功能在GNU C库中是多余的,因为在GNU C中,“ /”运算符总是四舍五入。但是在其他C实现中,“ /”可能会以否定的取舍方式不同。div和ldiv很有用,因为它们指定了如何将商数四舍五入为零。其余部分与分子的符号相同。


5
您指的是有关ANSI C的文本。这是C的相当老的规范。不确定该文本对于ANSI C是否正确,但对于C99绝对不正确。在C99§6.5.5中,整数除法定义为始终截断为零。
Palec 2014年

2

在这些约定源于的数学中,没有断言求模运算应该产生肯定的结果。

例如。

1 mod 5 = 1,但也可以等于-4。也就是说,1/5从0产生余数1或从5产生-4。(均为5的因子)

同样,-1 mod 5 = -1,但也可以等于4。也就是说,-1 / 5从0产生余数-1或从-5产生4。(均为5倍)

为了进一步阅读,请研究数学中的等价类


等效类是一个不同的概念,并且以非常严格的方式定义了模。比方说,我们有两个整数abb <> 0。根据欧几里得除法定理,恰好存在一对整数mr其中a = m * b + r0 <= r < abs( b )。所说的r是(数学)模运算的结果,根据定义是非负的。Wikipedia上的更多阅读内容和更多链接:en.wikipedia.org/wiki/Euclidean_division
Ister,

那不是真的 1 mod 5总是1。-4 mod 5也可能是1,但它们是不同的东西。
FelipeC

2

根据C99标准6.5.5节“ 乘法运算符”,需要满足以下条件:

(a / b) * b + a % b = a

结论

根据C99,余数运算结果的符号与被除数的符号相同。

我们来看一些示例(dividend / divisor):

当只有股息为负数时

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

当只有除数为负数时

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

当除数和股息均为负数时

(-3 / -2) * -2  +  -3 % -2 = -3

(-3 / -2) * -2 = -2

(-3 % -2) must be -1

6.5.5乘法运算符

句法

  1. 乘法表达式:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

约束条件

  1. 每个操作数应具有算术类型。运算符的操作数应为整数类型。

语义学

  1. 通常的算术转换是对操作数执行的。

  2. 二进制*运算符的结果是操作数的乘积。

  3. /运算符的结果是第一个操作数除以第二个的商。运算符的结果是余数。在这两个操作中,如果第二个操作数的值为零,则行为是不确定的。

  4. 当整数相除时,/运算符的结果为代数商,其中任何小数部分都被舍弃[1]。如果商a/b是可表示的,则表达式(a/b)*b + a%b应等于a

[1]:这通常称为“向零截断”。


1

模运算符给出余数。c中的模运算符通常采用分子的符号

  1. x = 5%(-3)-此处的分子为正,因此得出2
  2. y =(-5)%(3)-此处的分子为负数,因此结果为-2
  3. z =(-5)%(-3)-此处的分子为负,因此结果为-2

模数(余数)运算符也只能与整数类型一起使用,而不能与浮点数一起使用。


1
如果可以通过外部资源链接进行备份,那就太好了。
J ... S

1

我认为将mod其定义为抽象算术会更有用。不是算术运算,而是整体上不同的算术类别,不同的元素和不同的运算符。这意味着添加mod 3与“常规”添加不同;那是; 整数加法。

因此,当您这样做时:

5 % -3

您正在尝试将整数 5 映射到中的元素mod -3。这些是mod -3

{ 0, -2, -1 }

所以:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

假设您由于某种原因必须熬夜30个小时,那一天您还剩下几小时? 30 mod -24

但是C实现的不是mod,而是剩余的。无论如何,关键是返回负数确实有意义。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.