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 &lt;&lt; 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 &lt;&lt; 1) % c + a * ((b &gt;&gt; i) &amp; 1);,而sum 的值可能与a 一样大(又名,32 位无符号整数)。

sum大于2^31(大于0b'1000 0000 0000 0000 0000 0000 0000 0000)时,左移仍会导致溢出

【讨论】:

我明白了,溢出发生在sum = (sum &lt;&lt; 1) % c + a * ((b &gt;&gt; i) &amp; 1);,当a和b为2147483647且(sum @TPam for modmul 在 32 位 ALU 上使用朴素的 O(n^2) 或 Karatsuba 进行乘法运算,然后通过二进制除法对 2x32 位结果进行模运算(移位和减法)或使用 newton rapshon ...请参阅ALU32,了解如何对半位宽算术进行乘法和除法 ...

以上是关于C中按位运算的模运算的主要内容,如果未能解决你的问题,请参考以下文章

c语言中按位逻辑运算符位移运算符

uuid 创建中按位运算符的替代方案

c语言的按位运算符怎么操作!?

C语言中位移位运算符?

C/C++编程知识:运算符丨按位运算符,知识点详解

C的|||&&&异或~!运算