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

时间:2012-01-29 01:38:26

标签: c math gcc

我在C中有一个数组,我希望以类似于循环缓冲区的方式进行寻址,例如:a[-1]会返回数组的最后一个元素。

为了做到这一点,我尝试使用模运算(显然),问题是,当涉及负数时,我得到了相当奇怪的结果:

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

到目前为止,非常好。

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

问题:值(3295)确实适用于来自C标准{6.5}#6的(a/b)*b + a%b shall equal a, truncated towards zeroa=-1, b=4000)所以它不是错误< em>本身,但为什么是这样定义的标准?!当然,这里肯定有一些逻辑......

我如何撰写a%b以获得负面a的合理结果((a+b)%babs(a)>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 个答案:

答案 0 :(得分:9)

C中的算术始终(使用位移运算符的一些奇怪之处)在执行操作之前将所有操作数提升为公共类型。因此:

(-1) % 4000U

被提升为(假设32位整数):

0xffffffffu % 4000u

,产生3295。

如果要对可能为负的阵列偏移使用模运算,首先需要放弃在偏移上使用无符号算术。因此,由于C对有符号整数除法和余数的丑陋定义,您的结果现在将在-MAX_NUM+1MAX_NUM-1的范围内。如果代码不是性能关键,只需添加if (result<0) result+=MAX_NUM;并完成它。如果你真的需要避开分支(你已经测量以确定你需要避免它),那么再问一下如何优化这个计算,我自己或者比我更聪明的人肯定会能够提供帮助。 : - )

答案 1 :(得分:5)

正如6.5.3所说,“通常的算术转换是在操作数上执行的。”就你的例子而言:

(-1) % 4000U

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

“常用算术转换”在6.3.1.8中描述。