这是一个 C# 的代码片段:
https://dotnetfiddle.net/QbLowb
给定伪代码:
UInt64 a = 9228496132430806238;
UInt32 d = 585741;
我该如何计算
UInt32 r = a % d?
当然,问题在于我所用的编译器不支持
UInt64
数据类型。1但是我可以访问Windows ULARGE_INTEGER
结构体。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
或其他密码学大型多精度运算,或近似浮点数。
额外阅读材料
Microsoft Research: 计算机科学家的除法和模数 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支持
我希望有一种通用的解决方案来执行对ULARGE_INTEGER
(甚至LARGE_INTEGER
)进行模运算,而不需要本地64位支持的编译器。那将是正确、好、完美和理想的答案,其他人需要时也可以使用。
但是还有问题i的现实。这可能会导致一个通常对其他人无用的答案:
- 通过调用Win32大整数函数之一作弊(尽管没有模数函数)
- 通过使用有符号整数的64位支持来作弊
我可以检查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;
}
回答
花了一些时间,但我终于得到了答案。我会将其发布为答案;但是,由于某些人错误地认为我的独特问题是精确重复的,所以他们阻止了我。
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;
}
d
是否总是32位? - John Colemana
分成两半:上下两部分,一部分大于 Int64_MAX,另一部分小于等于 Int64_MAX,然后将它们转换为 Int64 类型,分别进行除法运算并存储余数,最后将两个余数相加并计算出最终的余数? - fejesed
是32位的。这意味着模数(即余数)也将是32位的。 - Ian Boydhi
和低位lo
,则a = 2^32 * hi + lo
,其余数为(pow(2,16)%d* pow(2,16)%d * hi %d + lo%d)%d
。如果您能找到一种不会溢出的模乘法方法,那么这应该可以解决问题。我不太明白您编辑的逻辑。如果UInt32 r = a%d; //对于a> = 0
总是有效的吗?如果2 ^ 63 <a <2 ^ 64
,似乎您会遇到麻烦 - John ColemanUInt64
强制转换为Int64
- 有符号整数。如果它没有触发符号位(即如果 UInt64 中的高位清除),那么我可以在编译器中使用有符号支持。 - Ian Boydmulhi
功能,或返回完整双宽乘积的乘法操作;是否支持 任何 64位操作或严格为Uint32
操作;是否有“计数前导零”操作可用)?例如,这将有助于通过牛顿拉弗森实现。 - njuffa