为进一步说明,我有2个32位寄存器,分别代表64位浮点数的高位和低位。我想计算它们的64位平方根。但是,虽然我没有64位平方根函数,但确实有32位平方根函数。
我的问题是:如果我想计算64位平方根,是否可以使用32位平方根对我有帮助,还是我试图进行牛顿-拉夫森(Newton-Raphson)或像这样的东西?
答案 0 :(得分:1)
可能没有
假设我们将64位整数变量i64
的两个部分分别设为hi和lo
sqrt(i64)= sqrt(hi * 2 32 + lo)
我们没有一种将和的平方根简化为另一个表达式的方法,因此我们无法从32位平方根计算出64位平方根
但是您说您有一个64位的浮点数值。您是否在没有FPU的平台上?您的32位平方根是浮点型还是整数型?无论如何,还是会出现相同的问题,因为尾数不能放在单个寄存器中,但是如果不需要全精度,您可以得到一些近似值
答案 1 :(得分:1)
您仍然需要对Newton-Raphson进行编程,但是您可以通过使用32位平方根求出32位近似值并将其用作Newton-Raphson的起始值来节省大量迭代。它将以更少的迭代次数收敛于完全正确的答案。这是一个值得节省的时间-硬件平方根有时使用表查找来查找Newton-Raphson的起点,最佳的理论复杂度计算假定您对较早的迭代使用较低的精度以节省时间。
答案 2 :(得分:1)
TL; DR 是。
取决于平台的硬件,工具链和数学库的功能和不足,这可能不一定允许以最快或最轻松的方式来计算双精度平方根。下面,我展示了一种基于Arnold Schönhage的平方根和倒数平方根的耦合迭代的简单方法:
从倒数平方根r approx 〜= 1 /√a的近似值开始,我们计算s 0 = a * r approx 和r 0 = r 约 / 2,然后进行迭代:
s i + 1 = s i + r i *(a-s i * s < sub> i )
r i + 1 = r i + r i *(1-r i * 2 * s i + 1 )
其中s i 近似为√a,r i 近似为1 /(2√a)。此迭代是巧妙地重新安排的Newton-Raphson迭代,因此具有二次收敛性,这意味着每个步骤将使正确位数大约增加一倍。从单精度r approx 开始,只需两步即可达到双精度精度。
如果我们现在利用常见的现代处理器支持并且通常可以通过功能fma()
访问的融合乘法加法运算(FMA),则每个半步仅需要两个FMA。另外,我们不需要特殊的舍入逻辑,因为FMA使用完整乘积a*b+c
计算a*b
,而无需应用任何截断或舍入。在ISO C99版本中给出的结果代码简短而有趣:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fenv.h>
#include <math.h>
double my_sqrt (double a)
{
double b, r, v, w;
float bb, rr, ss;
int e, t, f;
if ((a <= 0) || isinf (a) || isnan (a)) {
if (a < 0) {
r = 0.0 / 0.0;
} else {
r = a + a;
}
} else {
/* compute exponent adjustments */
b = frexp (a, &e);
t = e - 2*512;
f = t / 2;
t = t - 2 * f;
f = f + 512;
/* map argument into the primary approximation interval [0.25,1) */
b = ldexp (b, t);
bb = (float)b;
/* compute reciprocal square root */
ss = 1.0f / bb;
rr = sqrtf (ss);
r = (double)rr;
/* Use A. Schoenhage's coupled iteration for the square root */
v = 0.5 * r;
w = b * r;
w = fma (fma (w, -w, b), v, w);
v = fma (fma (r, -w, 1), v, v);
w = fma (fma (w, -w, b), v, w);
/* map back from primary approximation interval by jamming exponent */
r = ldexp (w, f);
}
return r;
}
/* Professor George Marsaglia's 64-bit KISS PRNG */
static uint64_t xx = 1234567890987654321ULL;
static uint64_t cc = 123456123456123456ULL;
static uint64_t yy = 362436362436362436ULL;
static uint64_t zz = 1066149217761810ULL;
static uint64_t tt;
#define MWC64 (tt = (xx << 58) + cc, cc = (xx >> 6), xx += tt, cc += (xx < tt), xx)
#define XSH64 (yy ^= (yy << 13), yy ^= (yy >> 17), yy ^= (yy << 43))
#define CNG64 (zz = 6906969069ULL * zz + 1234567ULL)
#define KISS64 (MWC64 + XSH64 + CNG64)
int main (void)
{
volatile union {
double f;
unsigned long long int i;
} arg, res, ref;
unsigned long long int count = 0ULL;
do {
arg.i = KISS64;
ref.f = sqrt (arg.f);
res.f = my_sqrt (arg.f);
if (res.i != ref.i) {
printf ("\n!!!! arg=% 23.16e %016llx res=% 23.16e %016llx ref=% 23.16e %016llx\n",
arg.f, arg.i, res.f, res.i, ref.f, ref.i);
}
count++;
if ((count & 0xffffff) == 0) printf ("\rtests = %llu", count);
} while (1);
return EXIT_SUCCESS;
}
在两个连续的Binade上对该代码进行详尽的测试将花费一小群机器大约一周左右的时间,这里我包括了一个使用随机操作数的快速“烟雾”测试。
在不支持FMA操作的硬件上,fma()
将基于仿真。这很慢,并且几种这样的仿真已被证明是错误的。 Schönhage迭代在没有FMA的情况下也可以正常工作,但是在这种情况下,必须添加其他舍入逻辑。在支持截断(零舍入)浮点乘法的情况下,最简单的解决方案是使用Tuckerman rounding。否则,可能有必要将双精度参数和初步结果重新解释为64位整数,并借助整数运算执行舍入。