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

Hub*_*rio 12 c math gcc

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

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

-1 % 4 = -1
-1 % 4U = 3
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

-1 % 4000 = -1
(-1+4000U) % 4000U = 3999
(-1) % 4000U = 3295
Run Code Online (Sandbox Code Playgroud)

问题:值(3295)确实适用于来自C标准(6.5.5#6)的(a/b)*b + a%b shall equal a, truncated towards zero(for a=-1, b=4000)所以它本身不是一个bug ,但为什么标准是这样定义的?!当然,这里必须有一些逻辑......

我如何写a%b以获得负面的合理结果a(因为(a+b)%b停止工作时abs(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;
}
Run Code Online (Sandbox Code Playgroud)

R..*_*R.. 9

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

(-1) % 4000U
Run Code Online (Sandbox Code Playgroud)

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

0xffffffffu % 4000u
Run Code Online (Sandbox Code Playgroud)

产生3295.

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


Car*_*rum 5

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

(-1) % 4000U
Run Code Online (Sandbox Code Playgroud)

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

"常用算术转换"在6.3.1.8中描述.