【发布时间】: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
或其他加密大型多精度运算,或近似浮点。
阅读奖励
- 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)执行模数的通用解决方案。这将是正确、良好、完美和理想的答案,其他人在需要时可以使用。
但现实中也存在i 的问题。它可能会导致一个对其他人通常无用的答案:
- 通过调用one of the Win32 large integer functions 作弊(虽然没有模数)
- 使用 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;
}
回答
花了一段时间,但我终于得到了答案。我会将其发布为答案;但是 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;
}
【问题讨论】:
-
所讨论的
d是否总是32 位? -
你不能 1) 将
a分成两半:Int64_MAX 上方和下方的部分,2) 通过存储两个提醒将它们转换为 Int64 以分别执行除法 3) 执行除法提醒 4) 添加结果并计算最终提醒? -
@JohnColeman 是的,就这个问题而言,您可以假设
d是 32 位的。这意味着模数(即余数)也将是 32 位。 -
请注意,如果您的大型 int 具有高部分
hi和低部分lo则a = 2^32*hi + lo其余部分是(pow(2,16)%d* pow(2,16)%d * hi %d + lo%d)%d,如果您能找到一种无溢出的模块化方法乘法,这应该有效。我不太遵循您编辑的逻辑。UInt32 r = a % d; //for a >= 0怎么能一直有效?2^63 < a < 2^64看来你有麻烦了 -
@JohnColeman 这是因为我将
UInt64转换为Int64- 一个有符号整数。如果它没有触发符号位打开(即,如果 UInt64 中的高位是明确的),那么我可以在编译器中使用签名支持。