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

注意:这个问题与
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数据类型的编译器中.但我可以访问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

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

《algorithm – 如何计算64位无符号整数的模数?》

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

《algorithm – 如何计算64位无符号整数的模数?》

u1 = a / r; //integer truncation math

《algorithm – 如何计算64位无符号整数的模数?》

v1 = a % r; //modulus

《algorithm – 如何计算64位无符号整数的模数?》

但是现在我让自己陷入了停滞状态.因为现在我必须计算:

v1||b / r

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

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

a^b mod n

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

奖金阅读

> Microsoft Research: Division and Modulus for Computer Scientists
> https://stackoverflow.com/questions/36684771/calculating-large-mods-by-hand
> Fastest way to calculate a 128-bit integer modulo a 64-bit integer(无关的问题;我恨你的人)

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

使用Int64支持

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

但也有我遇到的问题的现实.它可以导致一个通常对其他人没用的答案:

>通过拨打one of the Win32 large integer functions作弊(虽然模数没有)
>使用64位支持对有符号整数进行欺骗

我可以检查一下是否是积极的.如果是,我知道我的编译器对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

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

> Cant make value propagate through carry

作为mul的CPU程序集独立代码,请求了div.分频器正在解决你所有的问题.然而,它使用二进制长除法,因此它比堆叠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背后的想法很简单:

>定义

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

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

每个64位QWORD将被定义为高和低32位DWORD.
>对齐a,b

就像纸上的计算部门一样,我们必须对齐b所以它除了一个如此找到sh:

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

并计算m

(mh,ml) = 1<<sh

请注意,如果bh> = 0x80000000停止转移或我们将溢出…
>分裂

设置结果c = 0,然后简单地从a b> = a中减去b.对于每个减法,将m加到c.一旦b> a,则移位b,m再次对齐.如果m == 0或a == 0则停止.
>结果

c将保持64位的除法结果所以使用cl和类似a保持余数所以使用al作为你的模数结果.如果不发生溢出,你可以检查ch,ah是否为零(结果大于32位).对于像零除的边缘情况一样……

现在你想要64位/ 32位只需设置bh = 0 …为此我需要64位操作(, – ,<<,>>),我通过堆叠32位操作与Carry(即为什么我的ALU32类是在第一个创建的原因)更多信息请参阅上面的链接.

点赞