Algorithm 如何修复此浮点平方根算法

Algorithm 如何修复此浮点平方根算法,algorithm,matlab,math,floating-point,computer-science,Algorithm,Matlab,Math,Floating Point,Computer Science,我试图计算各种输入的IEEE-754 32位浮点平方根,但对于一个特定输入,以下基于牛顿-拉斐逊方法的算法不会收敛,我想知道我能做些什么来解决这个问题?对于我正在设计的平台,我有一个32位浮点加法器/减法器、乘法器和除法器 对于输入0x7F7FFFFF(3.4028234663852886E38),该算法不会收敛到正确答案18446743523953729536.000000。该算法的答案为1844674352395737728.000000 在硬件实现之前,我正在使用MATLAB实现我的代码。

我试图计算各种输入的IEEE-754 32位浮点平方根,但对于一个特定输入,以下基于牛顿-拉斐逊方法的算法不会收敛,我想知道我能做些什么来解决这个问题?对于我正在设计的平台,我有一个32位浮点加法器/减法器、乘法器和除法器

对于输入0x7F7FFFFF(3.4028234663852886E38),该算法不会收敛到正确答案18446743523953729536.000000。该算法的答案为1844674352395737728.000000

在硬件实现之前,我正在使用MATLAB实现我的代码。我只能使用单精度浮点值(因此没有双精度)

clc;清楚的全部关闭;
%输入
R=typecast(uint32(hex2dec(num2str)(dex2hex(((hex2dec('7F7FFFFF')))))),'single')
%初步估计
OneOverRoot2=单个(1/sqrt(2));
Root2=单个(sqrt(2));
%获取输入R的低位和高位
hexdata_high=bitand(位移位(hex2dec(num2hex(single(R))),-16),hex2dec('ffff');
hexdata_low=bitand(hex2dec(num2hex(single(R))),hex2dec('ffff');
%将输入的指数更改为-1以获得尾数
温度=位和(hexdata_高,hex2dec('807F');
Expo=bitshift(位与(hexdata_高,hex2dec('7F80')),-7);
hexdata_high=位(温度,hex2dec('3F00');
b=类型转换(uint32(hex2dec(num2str(dec2hex)((位移位(hexdata_高,16)+hexdata_低')),'single');
%如果指数是奇数。。。
国际单项体育联合会(比坦德(世博会,1))
%假设尾数[0.5…1.0]乘以2,因为世博会是奇数,
%现在它的值是[1.0…2.0]
%估计sqrt(尾数)为[1.0…sqrt(2))
%IOW:线性映射(0.5…1.0)到(1.0…sqrt(2))
尾数=(Root2-1.0)/(1.0-0.5)*(b-0.5)+1.0;
其他的
%尾数在[0.5…1.0]范围内
%估计sqrt(尾数)为[1/sqrt(2)…1.0)
%IOW:线性映射(0.5…1.0)到(1/sqrt(2)…1.0)
尾数=(1.0-OneOverRoot2)/(1.0-0.5)*(b-0.5)+OneOverRoot2;
结束
新闻=尾数*2^(比特移位(Expo-127,-1));
S=新闻
%S=(S+R/S)/2方法
对于j=1:6
fprintf('S%u%f%f\n',j,S,(S-sqrt(R));
S=单个((单个)+单个(单个(R)/单个(S))/2;
S=单个(S);
结束
准确度=(绝对稳定度((单次)-单次
差异=(abs((单个)(S)-单个(sqrt(单个(R)俎俎俎俎俎俎)
%获取十六进制输出
hexdata_high=(位与(位移位(hex2dec(num2hex(single)),-16),hex2dec('ffff'));
hexdata_low=(位与(hex2dec(num2hex(single)),hex2dec('ffff'));
fprintf('FLOAT:T输入:%e\T\T错误:%e\T\T我的答案:%e\n',R,sqrt(R),S);
fprintf('output hex=0x%04X%04X\n',hexdata高,hexdata低);
out=hex2dec(num2hex(单个));

我对此进行了猛烈的抨击。以下是我的想法:

float mysqrtf(float f) {
  if (f < 0) return 0.0f/0.0f;
  if (f == 1.0f / 0.0f) return f;
  if (f != f) return f;

  // half-ass an initial guess of 1.0.
  int expo;
  float foo = frexpf(f, &expo);
  float s = 1.0;
  if (expo & 1) foo *= 2, expo--;

  // this is the only case for which what's below fails.
  if (foo == 0x0.ffffffp+0) return ldexpf(0x0.ffffffp+0, expo/2);

  // do four newton iterations.
  for (int i = 0; i < 4; i++) {
   float diff = s*s-foo;
    diff /= s;
    s -= diff/2;
  }

  // do one last newton iteration, computing s*s-foo exactly.
  float scal = s >= 1 ? 4096 : 2048;
  float shi = (s + scal) - scal; // high 12 bits of significand
  float slo = s - shi; // rest of significand
  float diff = shi * shi - foo; // subtraction exact by sterbenz's theorem
  diff += 2 * shi * slo; // opposite signs; exact by sterbenz's theorem
  diff += slo * slo;
  diff /= s; // diff == fma(s, s, -foo) / s.
  s -= diff/2;

  return ldexpf(s, expo/2);
}
float mysqrtf(float f){
如果(f<0)返回0.0f/0.0f;
如果(f==1.0f/0.0f)返回f;
如果(f!=f)返回f;
//半个屁股一个1.0的初步猜测。
国际博览会;
float-foo=frexpf(f和expo);
浮点数s=1.0;
如果(世博会&1)foo*=2,世博会--;
//这是唯一一个以下内容失败的情况。
if(foo==0x0.ffffff p+0)返回ldexpf(0x0.ffffff p+0,expo/2);
//做四次牛顿迭代。
对于(int i=0;i<4;i++){
float diff=s*s-foo;
diff/=s;
s-=diff/2;
}
//做最后一次牛顿迭代,精确计算s*s-foo。
浮点数=s>=1?4096:2048;
float shi=(s+scal)-scal;//有效位的高12位
float slo=s-shi;//剩余有效位
float diff=shi*shi-foo;//用sterbenz定理精确减法
diff+=2*shi*slo;//相反的符号;根据sterbenz定理精确
diff+=slo*slo;
diff/=s;//diff==fma(s,s,-foo)/s。
s-=diff/2;
返回ldexpf(s,世博会/2);
}
首先要分析的是浮点运算中的公式
(s*s-foo)/s
。如果
s
sqrt(foo)
的一个足够好的近似值,则斯特本茨定理告诉我们分子在ulp(foo)内正确答案之一——所有这些误差都是计算得出的近似误差。
s*s
。然后我们除以
s
;这在最坏的情况下会给我们另一半ulp的近似误差。因此,即使没有融合乘加,
diff
也在1.5 ulp的范围内。我们将其除以2

请注意,初始猜测本身并不重要,只要你用足够的牛顿迭代进行后续操作

通过abs(s-foo/s)测量近似s到sqrt(foo)的误差。我最初猜测的误差最大为1。精确算术中的牛顿迭代将误差平方并除以4。浮点算术中的牛顿迭代——我做了四次——将误差平方,除以4,然后再踢进0.75 ulp的误差。你做了四次,你会发现你有一个相对的错误或者最多
0x0.000000 C4018384
,约为0.77 ulp。这意味着四次牛顿迭代会产生一个精确的四舍五入结果

我做了第五个牛顿步来得到一个正确的四舍五入的平方根。它工作的原因有点复杂

shi
持有
s
的“上半部分”,而
slo
持有“下半部分”。每个有效位的最后12位将为零。这意味着,特别是
shi*shi
shi*slo
slo*slo
可以精确地表示为
float
s

s*s
foo
的两个ULP范围内
shi*shi
s*s
的2047 ULP范围内。因此
shi*shi-foo
在零的2049 ULP范围内;特别是,它完全可以表示,并且小于2-10

您可以检查是否可以添加
2*shi*slo
并获得一个精确表示的结果,该结果在0的2-22范围内,然后添加
slo*slo
并获得一个精确计算的精确表示的结果--
s*s-foo

当你
float mysqrtf(float f) {
  if (f < 0) return 0.0f/0.0f;
  if (f == 1.0f / 0.0f) return f;
  if (f != f) return f;

  // half-ass an initial guess of 1.0.
  int expo;
  float foo = frexpf(f, &expo);
  float s = 1.0;
  if (expo & 1) foo *= 2, expo--;

  // this is the only case for which what's below fails.
  if (foo == 0x0.ffffffp+0) return ldexpf(0x0.ffffffp+0, expo/2);

  // do four newton iterations.
  for (int i = 0; i < 4; i++) {
   float diff = s*s-foo;
    diff /= s;
    s -= diff/2;
  }

  // do one last newton iteration, computing s*s-foo exactly.
  float scal = s >= 1 ? 4096 : 2048;
  float shi = (s + scal) - scal; // high 12 bits of significand
  float slo = s - shi; // rest of significand
  float diff = shi * shi - foo; // subtraction exact by sterbenz's theorem
  diff += 2 * shi * slo; // opposite signs; exact by sterbenz's theorem
  diff += slo * slo;
  diff /= s; // diff == fma(s, s, -foo) / s.
  s -= diff/2;

  return ldexpf(s, expo/2);
}