我正在寻找64位(无符号)立方根的快速代码。 (我正在使用C并使用gcc进行编译,但我认为所需的大部分工作都是语言和编译器无关的。)我将通过ulong表示一个64位的unisgned整数。
给定输入n我需要(整数)返回值r为
r * r * r <= n && n < (r + 1) * (r + 1) * (r + 1)
也就是说,我想要n的立方根,向下舍入。像
这样的基本代码return (ulong)pow(n, 1.0/3);
是不正确的,因为向范围的末尾舍入。像
这样简单的代码ulong
cuberoot(ulong n)
{
ulong ret = pow(n + 0.5, 1.0/3);
if (n < 100000000000001ULL)
return ret;
if (n >= 18446724184312856125ULL)
return 2642245ULL;
if (ret * ret * ret > n) {
ret--;
while (ret * ret * ret > n)
ret--;
return ret;
}
while ((ret + 1) * (ret + 1) * (ret + 1) <= n)
ret++;
return ret;
}
给出了正确的结果,但速度比它需要的慢。
此代码用于数学库,并且将从各种函数中多次调用它。速度很重要,但是你不能指望一个温暖的缓存(所以像2,642,245条目的二进制搜索这样的建议是正确的。)
为了进行比较,这里是正确计算整数平方根的代码。
ulong squareroot(ulong a) {
ulong x = (ulong)sqrt((double)a);
if (x > 0xFFFFFFFF || x*x > a)
x--;
return x;
}
答案 0 :(得分:10)
“Hacker's Delight”这本书有针对这个问题和许多其他问题的算法。代码在线here。 EDIT :该代码无法正常使用64位整数,本书中有关如何修复64位的说明有些令人困惑。正确的64位实现(包括测试用例)在线here。
我怀疑你的squareroot
函数是否正确“ - 对于参数应该是ulong a
,而不是n
:)(但同样的方法可以使用{{1}而不是cbrt
,尽管不是所有的C数学库都有立方根函数。)
答案 1 :(得分:3)
您可以尝试Newton的步骤来修复舍入错误:
ulong r = (ulong)pow(n, 1.0/3);
if(r==0) return r; /* avoid divide by 0 later on */
ulong r3 = r*r*r;
ulong slope = 3*r*r;
ulong r1 = r+1;
ulong r13 = r1*r1*r1;
/* making sure to handle unsigned arithmetic correctly */
if(n >= r13) r+= (n - r3)/slope;
if(n < r3) r-= (r3 - n)/slope;
单个牛顿步骤应该足够了,但是你可能有一个(或可能更多?)错误。您可以使用最终检查和增量步骤检查/修复这些,如OQ:
while(r*r*r > n) --r;
while((r+1)*(r+1)*(r+1) <= n) ++r;
或其他一些。
(我承认我很懒;正确的做法是仔细检查确定哪些(如果有的话)支票和增量实际上是必要的......)
答案 2 :(得分:3)
如果pow
过于昂贵,您可以使用count-leading-zeros指令来获得结果的近似值,然后使用查找表,然后使用一些牛顿步骤来完成它。
int k = __builtin_clz(n); // counts # of leading zeros (often a single assembly insn)
int b = 64 - k; // # of bits in n
int top8 = n >> (b - 8); // top 8 bits of n (top bit is always 1)
int approx = table[b][top8 & 0x7f];
鉴于b
和top8
,您可以使用查找表(在我的代码中,8K条目)来找到cuberoot(n)
的良好近似值。使用一些牛顿步骤(参见暴风雨的答案)来完成它。
答案 3 :(得分:2)
我已经适应了Modern Computer Arithmetic (Brent and Zimmerman)中1.5.2
( kth 根)中提出的算法。对于(k == 3)
而言,并给出了相对较高的初始猜测准确度-该算法的性能似乎优于上面的“ Hacker's Delight”代码。
不仅如此,MCA作为文本还提供了理论背景以及正确性和终止标准的证明。
假设我们可以提供一个“相对”良好的初始高估,我无法找到一个超过(7)次迭代的案例。 (这是否与具有2 ^ 6位的64位值有效相关?)无论哪种方式,它都是对HacDel代码中的(21)迭代的改进!
我使用的初始估算基于值( x )中有效位数的“舍入”。给定( x )中的( b )个有效位,我们可以说: 2^(b - 1) <= x < 2^b
。我声明没有证据(尽管应该相对容易证明): 2^ceil(b / 3) > x^(1/3)
这是我目前的代码...
static inline uint32_t u64_cbrt (uint64_t x)
{
#if (0) /* an exact IEEE-754 evaluation: */
if (x <= (UINT64_C(1) << (53)))
return (uint32_t) cbrt((double) x);
#endif
int bits_x = (64) - __builtin_clzll(x);
if (bits_x == 0)
return (0); /* cbrt(0) */
int exp_r = (bits_x + 2) / 3;
/* initial estimate: 2 ^ ceil(b / 3) */
uint64_t est_r = UINT64_C(1) << exp_r, r;
do /* quadratic convergence (?) */
{
r = est_r;
est_r = (2 * r + x / (r * r)) / 3;
}
while (est_r < r);
return ((uint32_t) r); /* floor(cbrt(x)) */
}
crbt
调用可能并没有那么有用-与sqrt
调用不同,它可以在现代硬件上以极高的效率实现。就是说,我看到2^53
下的一组值获得了收益(在IEEE-754中精确地表示为double),这让我感到惊讶。
唯一的缺点是除法: (r * r)
-这可能很慢,因为整数除法的延迟持续落后于ALU的其他进步。除以常量(3)
的除法运算是在任何现代优化编译器上通过倒数方法进行的。
答案 4 :(得分:1)
我会research how to do it by hand,然后将其转换为计算机算法,在基数2而不是基数10下工作。
我们最终得到的算法类似于(伪代码):
Find the largest n such that (1 << 3n) < input.
result = 1 << n.
For i in (n-1)..0:
if ((result | 1 << i)**3) < input:
result |= 1 << i.
我们可以通过观察按位 - 或相当于加法,重构为(result | 1 << i)**3
,缓存result**3 + 3 * i * result ** 2 + 3 * i ** 2 * result + i ** 3
和result**3
之间的值来优化result**2
的计算迭代,并使用移位而不是乘法。
答案 5 :(得分:1)
// On my pc: Math.Sqrt 35 ns, cbrt64 <70ns, cbrt32 <25 ns, (cbrt12 < 10ns)
// cbrt64(ulong x) is a C# version of:
// http://www.hackersdelight.org/hdcodetxt/acbrt.c.txt (acbrt1)
// cbrt32(uint x) is a C# version of:
// http://www.hackersdelight.org/hdcodetxt/icbrt.c.txt (icbrt1)
// Union in C#:
// http://www.hanselman.com/blog/UnionsOrAnEquivalentInCSairamasTipOfTheDay.aspx
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
public struct fu_32 // float <==> uint
{
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
public uint u;
}
private static uint cbrt64(ulong x)
{
if (x >= 18446724184312856125) return 2642245;
float fx = (float)x;
fu_32 fu32 = new fu_32();
fu32.f = fx;
uint uy = fu32.u / 4;
uy += uy / 4;
uy += uy / 16;
uy += uy / 256;
uy += 0x2a5137a0;
fu32.u = uy;
float fy = fu32.f;
fy = 0.33333333f * (fx / (fy * fy) + 2.0f * fy);
int y0 = (int)
(0.33333333f * (fx / (fy * fy) + 2.0f * fy));
uint y1 = (uint)y0;
ulong y2, y3;
if (y1 >= 2642245)
{
y1 = 2642245;
y2 = 6981458640025;
y3 = 18446724184312856125;
}
else
{
y2 = (ulong)y1 * y1;
y3 = y2 * y1;
}
if (y3 > x)
{
y1 -= 1;
y2 -= 2 * y1 + 1;
y3 -= 3 * y2 + 3 * y1 + 1;
while (y3 > x)
{
y1 -= 1;
y2 -= 2 * y1 + 1;
y3 -= 3 * y2 + 3 * y1 + 1;
}
return y1;
}
do
{
y3 += 3 * y2 + 3 * y1 + 1;
y2 += 2 * y1 + 1;
y1 += 1;
}
while (y3 <= x);
return y1 - 1;
}
private static uint cbrt32(uint x)
{
uint y = 0, z = 0, b = 0;
int s = x < 1u << 24 ? x < 1u << 12 ? x < 1u << 06 ? x < 1u << 03 ? 00 : 03 :
x < 1u << 09 ? 06 : 09 :
x < 1u << 18 ? x < 1u << 15 ? 12 : 15 :
x < 1u << 21 ? 18 : 21 :
x >= 1u << 30 ? 30 : x < 1u << 27 ? 24 : 27;
do
{
y *= 2;
z *= 4;
b = 3 * y + 3 * z + 1 << s;
if (x >= b)
{
x -= b;
z += 2 * y + 1;
y += 1;
}
s -= 3;
}
while (s >= 0);
return y;
}
private static uint cbrt12(uint x) // x < ~255
{
uint y = 0, a = 0, b = 1, c = 0;
while (a < x)
{
y++;
b += c;
a += b;
c += 6;
}
if (a != x) y--;
return y;
}