【问题标题】:Modulo multiplication (in C)模乘法(在 C 中)
【发布时间】:2012-02-10 09:06:04
【问题描述】:

具体来说:我有两个无符号整数 (a,b),我想计算 (a*b)%UINT_MAX(UINT_MAX 定义为最大无符号整数)。最好的方法是什么?

背景:我需要为 linux 编写一个模拟几何序列的模块,从中读取将给我下一个元素(模 UINT_MAX),我找到的唯一解决方案是将当前元素添加到自身时间,而添加是使用以下逻辑完成的:(我用于算术序列)

for(int i=0; i<b; ++i){
  if(UINT_MAX - current_value > difference) {
    current_value += difference;
  } else {
    current_value = difference - (UINT_MAX - current_value);
  }

当 current_value = a 在第一次迭代中(并且在每次迭代中更新,并且差异 = a(总是)。 显然这不是一个智能的解决方案。 一个聪明的人如何做到这一点?

谢谢!

【问题讨论】:

  • 不允许使用取模运算符或8字节整数类型吗?
  • 一个非常简单的愚蠢解决方案,其中“long long”是比 int 更长的类型。 long long 结果 = ((long long)a) * ((long long)b) % ((long long)UINT_MAX);
  • @JoachimIsaksson 结果不应该是 long long 类型,对吧?
  • 您可能可以使用中国剩余定理。请参阅这些问题Restore a number from several its remaindersSum and multiplication modulo
  • 我正在寻找一种也适用于系统中“最大”数据类型的解决方案,因此不是使用“long long”。 @ypercube CRT 在这种情况下很难使用,因为 UINT_MAX=(2^n)-1,不是吗?

标签: c math multiplication modulo


【解决方案1】:

如前所述,如果您有两倍可用宽度的类型,请在此处使用它

(unsigned int)(((unsigned long long)a * b) % UINT_MAX)

如果 int 是 32 位且 long long 是 64(或更多)。如果您没有更大的类型,您可以将因子拆分为位宽的一半,乘以和减少部分,最后组装它。此处为 32 位无符号说明:

a_low = a & 0xFFFF;  // low 16 bits of a
a_high = a >> 16;    // high 16 bits of a, shifted in low half
b_low = b & 0xFFFF;
b_high = b >> 16;
/*
 * Now a = (a_high * 65536 + a_low), b = (b_high * 65536 + b_low)
 * Thus a*b = (a_high * b_high) * 65536 * 65536
 *          + (a_high * b_low + a_low * b_high) * 65536
 *          + a_low * b_low
 *
 * All products a_i * b_j are at most (65536 - 1) * (65536 - 1) = UINT_MAX - 2 * 65536 + 2
 * The high product reduces to
 * (a_high * b_high) * (UINT_MAX + 1) = (a_high * b_high)
 * The middle products are a bit trickier, but splitting again solves:
 * m1 = a_high * b_low;
 * m1_low = m1 & 0xFFFF;
 * m1_high = m1 >> 16;
 * Then m1 * 65536 = m1_high * (UINT_MAX + 1) + m1_low * 65536 = m1_high + m1_low * 65536
 * Similar for a_low * b_high
 * Finally, add the parts and take care of overflow
 */
m1 = a_high * b_low;
m2 = a_low * b_high;
m1_low = m1 & 0xFFFF;
m1_high = m1 >> 16;
m2_low = m2 & 0xFFFF;
m2_high = m2 >> 16;
result = a_high * b_high;
temp = result + ((m1_low << 16) | m1_high);
if (temp < result)    // overflow
{
    result = temp+1;
}
else
{
    result = temp;
}
if (result == UINT_MAX)
{
    result = 0;
}
// I'm too lazy to type out the rest, you get the gist, I suppose.

当然,如果您实际上需要的是减少模 UINT_MAX + 1,就像 @Toad 假设的那样,那么这正是 unsigned int 的乘法。

【讨论】:

  • unsigned long long mod MAX_INT 的归约可以通过应用公式 (a*N+b)%(N-1) = (a + b)%(N-1) 来简化。跨度>
  • 是的,但是如果a+b溢出,你必须调整。如果有更大的类型可用,向上和向下转换就可以解决问题,而无需将产品分成高位和低位,因此在概念上更简单。
  • 同意它在概念上更简单,但技巧避免了昂贵的双字除法
  • 确实如此。虽然我不确定在今天的处理器上除法是否仍然比移位、添加和溢出检查慢。但既然你比我更了解这些事情,我会相信你的话。
  • 这里没有内幕消息。但是大多数 32 位处理器没有原生的“64 除以 32 得到 64”指令。 (他们可能有 64 div 32 到 32。)64 div 32 到 64 的情况通常在软件中实现。
【解决方案2】:

编辑:正如 cmets 中指出的那样......这个答案适用于 Modulo MAX_INT+1 我会把它放在这里,以备将来参考。

比这简单得多:

只需将两个无符号整数相乘,结果也将是一个无符号整数。 不适合 unsigned int 的所有内容基本上都不存在。 所以不需要做模运算:

See example here

 #include <stdio.h>

 void main()
 {
     unsigned int a,b;
     a = 0x90000000;
     b = 2;

     unsigned int c = a*b;

     printf("Answer is %X\r\n", c);
 }

答案是:0x20000000(所以它应该是 0x120000000,但是答案被截断了,正是你想要的模运算)

【讨论】:

  • 您似乎误读了问题以及第一个答案。 (现在已删除)OP 想要取模 UINT_MAX,而不是 UINT_MAX + 1
猜你喜欢
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 2015-05-24
  • 1970-01-01
  • 2020-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多