负数和无符号分母进行取模运算的奇怪结果

12

我在C语言中有一个数组,我想以类似于循环缓冲区的方式来访问它,例如:a[-1] 将返回数组的最后一个元素。

为了达到这个目的,我尝试使用模算术(显然),但问题是,当涉及负数时,我得到了相当奇怪的结果:

-1 % 4 = -1
-1 % 4U = 3

到目前为止,一切都很好。

-1 % 4000 = -1
(-1+4000U) % 4000U = 3999
(-1) % 4000U = 3295

问题:按照C标准(6.5.5#6),(a/b)*b + a%b应该等于a并向0截断,对于a=-1, b=4000,这个值为3295,因此它本质上不是一个bug,但是,为什么标准要这样定义呢?肯定存在某些逻辑...

我该如何编写a%b才能得到负数a的合理结果(当abs(a)>b(a+b)%b将停止工作)?

测试应用程序:

#include <stdio.h>
int main(int argc, char **argv) {
  int i=0;
#define MAX_NUM 4000U
  int weird = (i-1)%MAX_NUM;
  printf("%i\n", weird);
  printf("%i\n", (i-1+MAX_NUM))%MAX_NUM);
  printf("a: %i, b: %i, a from equation: %i\n", i-1, MAX_NUM,
    ((i-1)/MAX_NUM)*MAX_NUM + weird);
  return 0;
}
2个回答

9

C中的算术运算(除了一些比特位移操作的小怪异)在执行操作之前总是将所有操作数提升为一个通用类型。因此:

(-1) % 4000U

被推广为(假设32位整数):

0xffffffffu % 4000u

其结果为 3295。

如果你想在数组偏移量可能为负数的情况下使用模运算,你首先需要放弃在偏移量上使用无符号运算。因此,由于 C 的有符号整数除法和余数的定义很丑陋,你现在的结果范围将是 -MAX_NUM+1MAX_NUM-1。如果代码不是性能关键部分,只需添加 if (result<0) result+=MAX_NUM; 就可以了。如果你真的需要避免分支(而且你已经进行了度量以确定需要避免它),那么请再次询问如何优化此计算,我或其他在 Stack Overflow 上更聪明的人肯定能够帮助你。:-)


5
正如6.5.3所述,“操作数进行通常的算术转换”。在您的示例中:
(-1) % 4000U

这意味着将-1转换为unsigned int。 因此,您的-1实际上被解释为4294967295 ... 其余数恰好是您所看到的3295。

“通常的算术转换”在6.3.1.8中有描述。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接