Integer cube root
我正在寻找64位(无符号)多维数据集根的快速代码。(我正在使用C并用GCC编译,但我认为所需的大部分工作将是语言和编译器无关。)我将用ulong表示一个64位的未签名整数。
给定输入n,我要求(积分)返回值r为
1 | r * r * r <= n && n < (r + 1) * (r + 1) * (r + 1) |
也就是说,我要把n的立方根取整。基本代码类
1 |
不正确,因为舍入到范围的末尾。简单的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; } |
给出正确的结果,但速度比需要的慢。
此代码用于数学库,它将从各种函数中多次调用。速度很重要,但你不能指望一个温暖的缓存(所以像2642245条目的二进制搜索这样的建议是正确的)。
为了进行比较,下面是正确计算整数平方根的代码。
1 2 3 4 5 6 | ulong squareroot(ulong a) { ulong x = (ulong)sqrt((double)a); if (x > 0xFFFFFFFF || x*x > a) x--; return x; } |
这本书《黑客的喜悦》有解决这个和许多其他问题的算法。代码在这里在线。编辑:64位整数的代码不能正常工作,本书中关于如何修复64位整数的说明有些混乱。一个适当的64位实现(包括测试用例)在这里联机。
我怀疑你的
如果
1 2 3 4 | 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]; |
考虑到
您可以尝试牛顿的步骤来修复舍入误差:
1 2 3 4 5 6 7 8 9 10 11 | 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中:
1 2 | while(r*r*r > n) --r; while((r+1)*(r+1)*(r+1) <= n) ++r; |
或者一些这样的。
(我承认我很懒惰;正确的做法是仔细检查以确定哪些(如果有的话)检查和增量操作实际上是必要的…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | // 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; } |
我会研究如何用手来做,然后把它转换成一个计算机算法,在基2而不是基10中工作。
最后我们得到一个类似(伪代码)的算法:
1 2 3 4 5 | 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. |
我们可以通过观察按位或等价于加法,重构到