C中按位运算的模运算
Posted
技术标签:
【中文标题】C中按位运算的模运算【英文标题】:Modulo operation by bit operation in C 【发布时间】:2021-12-20 00:23:27 【问题描述】:说明
定义函数
unsigned mod(unsigned a, unsigned b, unsigned c)
;作用是计算并返回a*b%c的结果。测试a、b、c的范围要求大于0小于2^31,程序不能使用64位整数(如long long类型或__int64)求解。
问题:a*b 可能溢出(超出 32 位 unsigned int 类型的表示范围)。为了解决这个问题,可以使用以下算法。 假设无符号变量b的每个二进制位为xi(i=0,1,…,31),i=0为最低位,i=31为最高位,则
和
上式中,a*xi的结果要么是a要么是0; *2 运算可以通过左移1位来实现(小于2^31的整数 *2 结果必须小于2^32,不会溢出); %c的结果小于c,c小于2^31,和a之和不会溢出。 编写一个完整的程序,通过迭代的方式实现上述算法。
我的代码
#pragma warning(disable:4996)
#include <stdio.h>
unsigned mod(unsigned a, unsigned b, unsigned c)
unsigned sum = a * ((b >> 30) & 1);
for (int i = 29; i >= 0; i--)
sum = (sum << 1) % c + a * ((b >> i) & 1);
return sum % c;
int main()
//to achieve the subject requirements
unsigned a, b, c;
printf("Input unsigned integer numbers a, b, c:\n");
scanf("%u %u %u", &a, &b, &c);
printf("%u*%u%%%u=%u\n", a, b, c, mod(a, b, c));
//to verify output results
unsigned long long ab, bb, cb;
ab = a;
bb = b;
cb = c;
printf("%llu*%llu%%%llu=%llu", ab, bb, cb, ab * bb % cb);
问题
用较小的数字(如 100*500/3)进行计算时,结果是正确的。但是当数字接近问题的上限时(如2147483647*2147483647/3),你会得到错误的答案。我不知道这是为什么,因为我只是根据问题中给出的公式进行编程,我不知道数学原理。
【问题讨论】:
中间结果到底在什么时候不符合预期? 删除所有这些整数类型,只使用 stdint.h 中的uint64_t
。
【参考方案1】:
正如@Uduru 已经指出的那样,sum
可能会变得大于 2^31,因此,左移将导致溢出。
为防止这种情况发生,请记住以下几点:左移 1 与乘以 2 相同。因此,(sum << 1) % c
与 (sum * 2) % c
相同。现在,模数规则如下:
(a * b) mod c == ((a mod c) * (b mod c)) mod c.
因此您可以将代码更改为以下内容。
sum = ((sum % c) << 1) % c + a * ((b >> i) & 1);
因为c
保证小于2^31(根据引用部分),所以sum % c
也保证小于2^31。
【讨论】:
或者在函数中使用无符号长整数或uint64_t
之类的东西来获得更多空间我猜 LOL
如 OP 所述,不允许使用 64 位整数。【参考方案2】:
问题出在这里:
在mod()
中,您得到sum = (sum << 1) % c + a * ((b >> i) & 1);
,而sum
的值可能与a
一样大(又名,32 位无符号整数)。
当sum
大于2^31(大于0b'1000 0000 0000 0000 0000 0000 0000 0000
)时,左移仍会导致溢出。
【讨论】:
我明白了,溢出发生在sum = (sum << 1) % c + a * ((b >> i) & 1);
,当a和b为2147483647且(sum
@TPam for modmul 在 32 位 ALU 上使用朴素的 O(n^2) 或 Karatsuba 进行乘法运算,然后通过二进制除法对 2x32 位结果进行模运算(移位和减法)或使用 newton rapshon ...请参阅ALU32,了解如何对半位宽算术进行乘法和除法 ...以上是关于C中按位运算的模运算的主要内容,如果未能解决你的问题,请参考以下文章