如何计算64位无符号整数的模数?

时间:2016-04-24 13:30:07

标签: algorithm math modulo

注意:此问题与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

为了简化,我们可以使用变量:

enter image description here

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

enter image description here

u1 = a / r; //integer truncation math

enter image description here

v1 = a % r; //modulus

enter image description here

但现在我已经陷入停滞状态了。因为现在我必须计算:

v1||b / r

换句话说,我必须执行64位值的划分,这是我首先无法执行的!

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

a^b mod n

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

奖金阅读

1 但它确实支持Int64,但我不认为这有助于我

使用Int64支持

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

的问题也存在。它可以导致一个通常对其他人没用的答案:

我可以检查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

1 个答案:

答案 0 :(得分:1)

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

已请求

mul,div CPU 程序集独立代码。分频器正在解决您的所有问题。但是,它使用的是二进制长除法,因此与堆积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位和低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>=0x80000000停止换档,否则我们会溢出...

  3. 设置结果c = 0,然后在b期间从a中减去b>=a。对于每个减法,将m添加到cb>a移至两个b,m后再向右对齐。如果m==0a==0停止。

  4. 结果

    c将保留64位除法结果,因此请使用cl,类似地a将保留余数,因此请使用al作为模数结果。如果未发生溢出(结果大于32位),则可以检查ch,ah是否为零。边缘情况也是如此,例如被零除...

现在,您只需设置bh=0即可...为此,我需要64位操作(+,-,<<,>>),我通过与 Carry (这就是为什么我的 ALU32 类首先创建的原因),有关更多信息,请参见上面的链接。