我目前正在编写一个快速的32.32定点数学库。我成功地使加法,减法和乘法工作正常,但我完全坚持分裂。
对于那些不记得的人提醒一下:32.32定点数是一个具有32位整数部分和32位小数部分的数字。
我提出的最佳算法需要96位整数除法,编译器通常没有内置函数。
无论如何,这里是:
G = 2^32
notation: x is the 64-bit fixed-point number, x1 is its low nibble and x2 is its high
G*(a/b) = ((a1 + a2*G) / (b1 + b2*G))*G // Decompose this
G*(a/b) = (a1*G) / (b1*G + b2) + (a2*G*G) / (b1*G + b2)
如您所见,(a2*G*G)
保证大于常规的64位整数。如果我的编译器实际上支持uint128_t,我只需执行以下操作:
((uint128_t)x << 32) / y)
他们不是,我需要一个解决方案。谢谢你的帮助。
答案 0 :(得分:7)
您可以将较大的分区分解为多个以较少位进行除法的块。正如另一张已经提到过的海报,该算法可以在Knuth的TAOCP中找到。
然而,没有必要买这本书!
黑客高兴网站上有一个代码在C中实现了算法。它是用32位算术编写的64位无符号除法,所以你不能直接删除代码。要从64位到128位,你必须将所有类型,掩模和常数加宽两个,例如short变为int,0xffff变为0xffffffffll等。
在这个简单易行的改变后,您应该可以进行128位分割。
代码在这里:http://www.hackersdelight.org/HDcode/divlu.c(由于行结尾,可能会在网络浏览器中严重包装。如果是这样,只需保存代码并用记事本左右打开它。)
由于您的最大值仅需要96位,因此64位除法之一将始终返回零,因此您甚至可以简化代码。
哦 - 在我忘记之前:代码只适用于无符号值。要从有符号转换为无符号除法,您可以执行以下操作(伪代码样式):
fixpoint Divide (fixpoint a, fixpoint b)
{
// check if the integers are of different sign:
fixpoint sign_difference = a ^ b;
// do unsigned division:
fixpoint x = unsigned_divide (abs(a), abs(b));
// if the signs have been different: negate the result.
if (sign_difference < 0)
{
x = -x;
}
return x;
}
网站本身也值得一试:http://www.hackersdelight.org/
希望它有所帮助。
顺便说一下 - 你正在做的好任务..你介意告诉我们你需要什么定点库吗?Btw - 用于除法的普通移位和减法算法也可以。
如果您定位x86,则可以使用MMX或SSE内在函数实现它。该算法仅依赖于原始操作,因此它的执行速度也非常快。
答案 1 :(得分:1)
更好的自我调整答案:
原谅C#-ism的答案,但以下内容应适用于所有情况。可能有一种解决方案可以找到正确的转变以便更快地使用,但我必须比现在更深入地思考。这应该合理有效:
int upshift = 32;
ulong mask = 0xFFFFFFFF00000000;
ulong mod = x % y;
while ((mod & mask) != 0)
{
// Current upshift of the remainder would overflow... so adjust
y >>= 1;
mask <<= 1;
upshift--;
mod = x % y;
}
ulong div = ((x / y) << upshift) + (mod << upshift) / y;
简单但不安全的回答:
如果此余数在高32位中设置了任何位,则此计算可能导致x % y
余数的升档溢出,从而导致错误答案。
((x / y) << 32) + ((x % y) << 32) / y
第一部分使用整数除法,并给出答案的高位(将它们向上移动)。
第二部分计算高位除法的剩余部分(无法进一步分割的位)的低位,向上移位然后分割。
答案 2 :(得分:1)
快速肮脏。
A / B除以双精度浮点。 这给你C~ = A / B.由于浮点精度和53位尾数,它仅近似。
将C舍入到定点系统中的可表示数字。
现在计算(再次使用你的固定点)D = A-C * B.这应该具有比A更低的幅度。
重复,现在使用浮点计算D / B.再次,将答案舍入为整数。随时添加每个分区结果。当你的余数太小以至于你的浮点除数在四舍五入后返回0时,你可以停止。
你还没有完成。现在你非常接近答案,但这些分歧并不准确。 要完成,您必须进行二分查找。使用(非常好的)起始估计,看看是否增加它可以改善错误..你基本上想要包括正确的答案,并用新的测试将范围分成两半。
是的,您可以在这里进行牛顿迭代,但二进制搜索可能会更容易,因为您只需要简单的乘法并使用现有的32.32精度工具包进行添加。
这是不最有效的方法,但它是迄今为止最容易编码的方法。
答案 3 :(得分:0)
我喜欢Nils的回答,这可能是最好的。这只是长时间的划分,就像我们在小学里所学到的一样,除了数字是基数2 ^ 32而不是基数10。
但是,您也可以考虑使用 Newton的近似方法进行划分:
x := x (N + N - N * D * x)
其中N是分子,D是恶魔。
这只是使用你已经拥有的乘法和加法,并且它很快收敛到大约1 ULP的精度。另一方面,在所有情况下,您都无法获得确切的0.5-ULP答案。
在任何情况下,棘手的位都是检测和处理溢出。