带负数的模运算

Alv*_*lva 172 c gcc modulo

在ac程序中我正在尝试以下操作(只是检查行为)

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

printf("%d ,%d ,%d", x, y, z); 
Run Code Online (Sandbox Code Playgroud)

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

Arj*_*kar 151

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

  • 理解你所描述的是mod的常见int(a/b)(截断)描述.但规则也可能是下限(a/b)(Knuth).在Knuth的情况下,`-5/3`是`-2`并且mod变为1.简而言之:一个模块有一个跟随被除数符号(truncate)的符号,另一个模块有一个跟随符号的符号(高德纳). (10认同)
  • 这是 C 标准完全不是我想要的情况。我从来没有想要截断为零或负模数,但经常想要相反的并且需要解决 C。 (3认同)

oua*_*uah 131

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

模数和余数运算符在负值方面不同.

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

C定义了以下%操作a % b:

  a == (a / b * b) + a % b
Run Code Online (Sandbox Code Playgroud)

/截断的整数除法0.这是朝向0(并且不是负向无效)的截断,其将定义%为余数运算符而不是模运算符.

  • @gronostaj不在CS.查看更高级别的语言,如Haskell或Scheme,它们都定义了两个不同的运算符(在Scheme中为`remaining`和`modulo`,在Haskell中为`rem`和`mod`).这些运算符规范在这些语言上的划分方式不同:截断为0或朝向负无穷大.顺便说一句,C标准从不调用`%`*模数运算符*,它们只是将它命名为*%运算符*. (38认同)
  • 根据定义,[余数是模运算的结果](https://en.wikipedia.org/wiki/Remainder).应该没有余数运算符这样的东西,因为没有余数运算这样的东西,它被称为模数. (6认同)
  • 不要与 C 中的 `remainder` _function_ 混淆,后者在除法中使用舍入到最近的语义实现 IEEE 余数 (4认同)

小智 61

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

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

int remainder(int a, int b)
{
    return a - (a / b) * b;
}
Run Code Online (Sandbox Code Playgroud)

对于模运算,我们可以有以下函数(假设b> 0)

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}
Run Code Online (Sandbox Code Playgroud)

我的结论是(a%b)在C中是余数运算符而非模运算符.

  • 当'b`为负时(实际上对于`r`和`b`都为负,它给出的结果小于`-b`),这不会给出正面结果.为确保所有输入的正结果,您可以使用`r + abs(b)`或匹配`b`s符号,您可以将条件更改为`r*b <0`. (3认同)

Uda*_*ukh 52

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

找到正模的一个简单函数就是这个 -

int modulo(int x,int N){
    return (x % N + N) %N;
}
Run Code Online (Sandbox Code Playgroud)

假设N为正,这将适用于x的正值和负值.

PS也如@chux所指出的,如果你的x和N分别达到INT_MAX-1和INT_MAX,只需替换N > 0N + N - 1 <= INT_MAX.

如果他们也超过了长期的限制(即接近LLONG_MAX),那么你应该分别处理正面和负面情况,如其他答案所述.

  • 请注意,当 `N &lt; 0` 时,结果可能是负数,如 `modulo(7, -3) --&gt; -2`。此外`x % N + N` 可以溢出`int` 数学,这是未定义的行为。例如`modulo(INT_MAX - 1,INT_MAX)` 可能会导致-3。 (2认同)

Yu *_*Hao 8

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

请注意,在C89中,向上或向下的结果是实现定义的.因为在所有标准中都是(a/b) * b + a%b等于a,所以%涉及负操作数的结果也是在C89中实现定义的.


Ric*_*ali 5

根据C99 标准,第6.5.5 节乘法运算符,需要以下内容:

(a / b) * b + a % b = a
Run Code Online (Sandbox Code Playgroud)

结论

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

让我们看一些例子(dividend / divisor):

当只有股息为负数时

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

(-3 / 2) * 2 = -2

(-3 % 2) must be -1
Run Code Online (Sandbox Code Playgroud)

当只有除数为负数时

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

(3 / -2) * -2 = 2

(3 % -2) must be 1
Run Code Online (Sandbox Code Playgroud)

当除数和被除数均为负数时

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

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

(-3 % -2) must be -1
Run Code Online (Sandbox Code Playgroud)

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]:这通常称为“向零截断”。


chu*_*ica 5

模数可以为负吗?

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

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  
Run Code Online (Sandbox Code Playgroud)

所需的运算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   
Run Code Online (Sandbox Code Playgroud)


Kar*_*and 2

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

这是参考

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

整数除法

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

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