对于定点数学库,我需要一个快速的96位64位特定除法算法

时间:2009-06-08 07:21:57

标签: c algorithm division performance

我目前正在编写一个快速的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)

他们不是,我需要一个解决方案。谢谢你的帮助。

4 个答案:

答案 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答案。

在任何情况下,棘手的位都是检测和处理溢出。