注意:此问题与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; //integer truncation math
v1 = a % r; //modulus
但现在我已经陷入停滞状态了。因为现在我必须计算:
v1||b / r
换句话说,我必须执行64位值的划分,这是我首先无法执行的!
这已经是一个已经解决的问题了。但我能在Stackoverflow上找到的唯一问题是人们试图计算:
a^b mod n
或其他加密大型多精度操作,或近似浮点。
1 但它确实支持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;
}
答案 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
背后的想法很简单:
定义
让我们假设我们得到64/64位除法(模将是部分乘积),并希望使用32位算术,所以:
(ah,al) / (bh,bl) = (ch,cl)
每个64位QWORD将被定义为高32位和低32位DWORD。
对齐a,b
就像纸上的计算部门一样,我们必须对齐b
以便将a
进行划分,因此找到sh
:
(bh,bl)<<sh <= (ah,al)
(bh,bl)<<(sh+1) > (ah,al)
然后计算m
(mh,ml) = 1<<sh
请注意,如果bh>=0x80000000
停止换档,否则我们会溢出...
除
设置结果c = 0
,然后在b
期间从a
中减去b>=a
。对于每个减法,将m
添加到c
。 b>a
移至两个b,m
后再向右对齐。如果m==0
或a==0
停止。
结果
c
将保留64位除法结果,因此请使用cl
,类似地a
将保留余数,因此请使用al
作为模数结果。如果未发生溢出(结果大于32位),则可以检查ch,ah
是否为零。边缘情况也是如此,例如被零除...
现在,您只需设置bh=0
即可...为此,我需要64位操作(+,-,<<,>>)
,我通过与 Carry (这就是为什么我的 ALU32 类首先创建的原因),有关更多信息,请参见上面的链接。