我在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 zero
(a=-1, b=4000
)所以它不是错误< em>本身,但为什么是这样定义的标准?!当然,这里肯定有一些逻辑......
我如何撰写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;
}
答案 0 :(得分:9)
C中的算术始终(使用位移运算符的一些奇怪之处)在执行操作之前将所有操作数提升为公共类型。因此:
(-1) % 4000U
被提升为(假设32位整数):
0xffffffffu % 4000u
,产生3295。
如果要对可能为负的阵列偏移使用模运算,首先需要放弃在偏移上使用无符号算术。因此,由于C对有符号整数除法和余数的丑陋定义,您的结果现在将在-MAX_NUM+1
到MAX_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中描述。