【问题标题】:How to calculate modulus of 64-bit unsigned integer?如何计算 64 位无符号整数的模数?
【发布时间】:2016-08-17 20:28:16
【问题描述】:

注意:这个问题与Fastest way to calculate a 128-bit integer modulo a 64-bit integer不同。


这是一个 C# 小提琴:

https://dotnetfiddle.net/QbLowb


给定伪代码:

UInt64 a = 9228496132430806238;
UInt32 d = 585741;

如何计算

UInt32 r = a % d?

当然,问题是我不在支持 UInt64 数据类型的编译器中。1 但我确实可以访问 Windows ULARGE_INTEGER union

typedef struct ULARGE_INTEGER {
   DWORD LowPart;
   DWORD HighPart;
};

这意味着我可以将上面的代码变成:

//9228496132430806238 = 0x80123456789ABCDE
UInt32 a = 0x80123456; //high part
UInt32 b = 0x789ABCDE; //low part
UInt32 r = 585741;     

怎么做

但现在是如何进行实际计算。我可以从纸笔长除法开始:

       ________________________  
585741 ) 0x80123456  0x789ABCDE

为了更简单,我们可以在变量中工作:

现在我们完全使用 32 位无符号类型,我的编译器支持

u1 = a / r; //整数截断数学

v1 = a % r; //模数

但现在我让自己陷入了停顿。因为现在我必须计算:

v1||b / r

换句话说,我必须执行 64 位值的除法,这是我一开始无法执行的!

这一定是一个已经解决的问题。但我在 Stackoverflow 上能找到的唯一问题是人们试图计算:

a^b mod n

或其他加密大型多精度运算,或近似浮点。

阅读奖励

1但它确实支持Int64,但我认为这对我没有帮助

使用 Int64 支持

我希望在没有本机 64 位支持的编译器中针对 ULARGE_INTEGER(甚至是 LARGE_INTEGER)执行模数的通用解决方案。这将是正确、良好、完美和理想的答案,其他人在需要时可以使用。

但现实中也存在i 的问题。它可能会导致一个对其他人通常无用的答案:

我可以检查a 是否为正。如果是,我知道我的编译器对Int64 的内置支持将处理:

UInt32 r = a % d; //for a >= 0

然后是如何处理另一种情况:a 是否定的

UInt32 ModU64(ULARGE_INTEGER a, UInt32 d)
{
   //Hack: Our compiler does support Int64, just not UInt64.
   //Use that Int64 support if the high bit in a isn't set.
   Int64 sa = (Int64)a.QuadPart;
   if (sa >= 0) 
      return (sa % d);

   //sa is negative. What to do...what to do.

   //If we want to continue to work with 64-bit integers,
   //we could now treat our number as two 64-bit signed values:
   // a == (aHigh + aLow)
   //       aHigh = 0x8000000000000000
   //       aLow  = 0x0fffffffffffffff
   //
   // a mod d = (aHigh + aLow) % d
   //         = ((aHigh % d) + (aLow % d)) % d //<--Is this even true!?

   Int64 aLow  = sa && 0x0fffffffffffffff;
   Int64 aHigh =       0x8000000000000000;

   UInt32 rLow  = aLow  % d; //remainder from low portion
   UInt32 rHigh = aHigh % d; //this doesn't work, because it's "-1 mod d"

   Int64 r = (rHigh + rLow) % d;

   return d;
}

回答

花了一段时间,但我终于得到了答案。我会将其发布为答案;但是 Z29kIGZ1Y2tpbmcgZGFtbiBzcGVybSBidXJwaW5nIGNvY2tzdWNraW5nIHR3YXR3YWZmbGVz 人们错误地认为我的独特问题是完全重复的。

UInt32 ModU64(ULARGE_INTEGER a, UInt32 d)
{
   //I have no idea if this overflows some intermediate calculations
   UInt32 Al = a.LowPart; 
   UInt32 Ah = a.HighPart;

   UInt32 remainder = (((Ah mod d) * ((0xFFFFFFFF - d) mod d)) + (Al mod d)) mod d;

   return remainder;
}

Fiddle

【问题讨论】:

  • 所讨论的d 是否总是32 位?
  • 你不能 1) 将a 分成两半:Int64_MAX 上方和下方的部分,2) 通过存储两个提醒将它们转换为 Int64 以分别执行除法 3) 执行除法提醒 4) 添加结果并计算最终提醒?
  • @JohnColeman 是的,就这个问题而言,您可以假设 d 是 32 位的。这意味着模数(即余数)也将是 32 位。
  • 请注意,如果您的大型 int 具有高部分 hi 和低部分 loa = 2^32*hi + lo 其余部分是 (pow(2,16)%d* pow(2,16)%d * hi %d + lo%d)%d,如果您能找到一种无溢出的模块化方法乘法,这应该有效。我不太遵循您编辑的逻辑。 UInt32 r = a % d; //for a &gt;= 0 怎么能一直有效? 2^63 &lt; a &lt; 2^64 看来你有麻烦了
  • @JohnColeman 这是因为我将 UInt64 转换为 Int64 - 一个有符号整数。如果它没有触发符号位打开(即,如果 UInt64 中的高位是明确的),那么我可以在编译器中使用签名支持。

标签: algorithm math modulo


【解决方案1】:

我刚刚在这个相关的QA中更新了我的 ALU32 类代码:

由于请求了mul,divCPU 独立于程序集的代码。分频器正在解决你所有的问题。然而,它使用 二进制长除法,所以它比堆叠 32 位 mul/mod/div 操作有点懒散。这里是代码的相关部分:

void ALU32::div(DWORD &c,DWORD &d,DWORD ah,DWORD al,DWORD b)
    {
    DWORD ch,cl,bh,bl,h,l,mh,ml;
    int e;
    // edge cases
    if (!b ){ c=0xFFFFFFFF; d=0xFFFFFFFF; cy=1; return; }
    if (!ah){ c=al/b;       d=al%b;       cy=0; return; }
    // align a,b for binary long division m is the shifted mask of b lsb
    for (bl=b,bh=0,mh=0,ml=1;bh<0x80000000;)
        {
        e=0; if (ah>bh) e=+1;   // e = cmp a,b {-1,0,+1}
        else if (ah<bh) e=-1;
        else if (al>bl) e=+1;
        else if (al<bl) e=-1;
        if (e<=0) break;        // a<=b ?
        shl(bl); rcl(bh);       // b<<=1
        shl(ml); rcl(mh);       // m<<=1
        }
    // binary long division
    for (ch=0,cl=0;;)
        {
        sub(l,al,bl);           // a-b
        sbc(h,ah,bh);
        if (cy)                 // a<b ?
            {
            if (ml==1) break;
            shr(mh); rcr(ml);   // m>>=1
            shr(bh); rcr(bl);   // b>>=1
            continue;
            }
        al=l; ah=h;             // a>=b ?
        add(cl,cl,ml);          // c+=m
        adc(ch,ch,mh);
        }
    cy=0; c=cl; d=al;
    if ((ch)||(ah)) cy=1;       // overflow
    }

查看链接的 QA 以了解类的描述和使用的子功能。 a/b 背后的想法很简单:

  1. 定义

    假设我们得到了 64/64 位除法(模数将是部分乘积)并且想要使用 32 位算术,所以:

    (ah,al) / (bh,bl) = (ch,cl)
    

    每个 64 位 QWORD 将被定义为高和低 32 位 DWORD。

  2. 对齐a,b

    就像纸上的计算除法一样,我们必须对齐b,所以它除以a,所以找到sh

    (bh,bl)<<sh <= (ah,al)
    (bh,bl)<<(sh+1) > (ah,al)
    

    然后计算m 所以

    (mh,ml) = 1<<sh
    

    请注意,如果bh&gt;=0x80000000 停止移位,否则我们会溢出...

  3. 设置结果c = 0,然后简单地从a 中减去bb&gt;=a。对于每个减法,将m 添加到c。一旦b&gt;ab,m 都向右移动以再次对齐。如果m==0a==0 则停止。

  4. 结果

    c 将保存 64 位除法结果,因此使用 cl 并且类似地 a 保存余数,因此使用 al 作为模数结果。如果没有发生溢出,您可以检查ch,ah 是否为零(因为结果大于 32 位)。像除以零这样的边缘情况也是如此......

现在您想要 64 位/32 位,只需设置 bh=0 ... 为此,我需要 64 位操作 (+,-,&lt;&lt;,&gt;&gt;),我通过将 32 位操作与 Carry 叠加来实现(这就是原因为什么我的 ALU32 类首先被创建)有关更多信息,请参见上面的链接。

【讨论】:

    猜你喜欢
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-11
    • 2011-11-10
    • 2016-06-28
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多