C 这个浮点平方根近似是如何工作的?
我发现C 这个浮点平方根近似是如何工作的?,c,c++,optimization,floating-point,ieee-754,C,C++,Optimization,Floating Point,Ieee 754,我发现floats有一个非常奇怪但有效的平方根近似值;我真的不明白。有人能解释一下为什么这个代码有效吗 float sqrt(float f) { const int result = 0x1fbb4000 + (*(int*)&f >> 1); return *(float*)&result; } 我已经试过一点了。我知道地震III,我猜这是类似的(没有牛顿迭代),但我真的很想解释一下它是如何工作的 (NOTA:我已经把它都标记了,因为它既是
float
s有一个非常奇怪但有效的平方根近似值;我真的不明白。有人能解释一下为什么这个代码有效吗
float sqrt(float f)
{
const int result = 0x1fbb4000 + (*(int*)&f >> 1);
return *(float*)&result;
}
我已经试过一点了。我知道地震III,我猜这是类似的(没有牛顿迭代),但我真的很想解释一下它是如何工作的
(NOTA:我已经把它都标记了,因为它既是有效的ISH(见注释)C和C++代码)
< P> <代码>(*(int *)和f> 1)< /code >右移位“代码> f< /code >的位表示。这几乎将指数除以2,这大致相当于取平方根。1 为什么差不多?在IEEE-754中,实际指数是e-127.2。要将其除以2,我们需要e/2-64,但上面的近似值只给出了e/2-127。所以我们需要在得到的指数上加上63。这是由该神奇常数(0x1fbb4000
)的第30-23位贡献的
我可以想象魔法常数的剩余部分被选择来最小化尾数范围内的最大误差,或者类似的东西。然而,目前尚不清楚它是通过分析、迭代还是启发式确定的
值得指出的是,这种方法有些不可移植。它(至少)做出以下假设:
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <vector>
using std::cout;
using std::endl;
using std::size_t;
using std::sqrt;
using std::uint32_t;
template <typename T, typename U>
inline T reinterpret(const U x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it reads an inactive union member.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
union tu_pun {
U u = U();
T t;
};
const tu_pun pun{x};
return pun.t;
}
constexpr float source = -0.1F;
constexpr uint32_t target = 0x5ee66666UL;
const uint32_t after_rshift = reinterpret<uint32_t,float>(source) >> 1U;
const bool is_little_endian = after_rshift == target;
float est_sqrt(const float x)
/* A fast approximation of sqrt(x) that works less well for subnormal numbers.
*/
{
static_assert( std::numeric_limits<float>::is_iec559, "" );
assert(is_little_endian); // Could provide alternative big-endian code.
/* The algorithm relies on the bit representation of normal IEEE floats, so
* a subnormal number as input might be considered a domain error as well?
*/
if ( std::isless(x, 0.0F) || !std::isfinite(x) )
return std::numeric_limits<float>::signaling_NaN();
constexpr uint32_t magic_number = 0x1fbb4000UL;
const uint32_t raw_bits = reinterpret<uint32_t,float>(x);
const uint32_t rejiggered_bits = (raw_bits >> 1U) + magic_number;
return reinterpret<float,uint32_t>(rejiggered_bits);
}
int main(void)
{
static const std::vector<float> test_values{
4.0F, 0.01F, 0.0F, 5e20F, 5e-20F, 1.262738e-38F };
for ( const float& x : test_values ) {
const double gold_standard = sqrt((double)x);
const double estimate = est_sqrt(x);
const double error = estimate - gold_standard;
cout << "The error for (" << estimate << " - " << gold_standard << ") is "
<< error;
if ( gold_standard != 0.0 && std::isfinite(gold_standard) ) {
const double error_pct = error/gold_standard * 100.0;
cout << " (" << error_pct << "%).";
} else
cout << '.';
cout << endl;
}
return EXIT_SUCCESS;
}
- 该平台使用单精度IEEE-754进行浮点运算
表示的尾数float
- 您将不受未定义行为的影响,因为这种方法违反了C/C++
sqrtf
!)相比提供了有用的加速,否则应该避免使用它
一,。sqrt(a^b)=(a^b)^0.5=a^(b/2) 二,。参见例如,设y=sqrt(x) 根据对数的性质,log(y)=0.5*log(x)(1) 将正常的
float
解释为整数会得到INT(x)=Ix=L*(log(x)+B-σ)(2)
其中L=2^N,N为有效位位数,B为指数偏差,σ为调整近似值的自由因子
将(1)和(2)组合得到:Iy=0.5*(Ix+(L*(B-σ)))
在代码中写为(*(int*)&x>>1)+0x1FB4000代码>
找到σ,使常数等于0x1fbb4000,并确定其是否为最佳值。添加wiki测试工具以测试所有浮点值
对于许多
浮点
,近似值在4%以内,但对于次正态数,近似值非常差
请注意,参数为+/-0.0时,结果不是零
printf("% e % e\n", sqrtf(+0.0), sqrt_apx(0.0)); // 0.000000e+00 7.930346e-20
printf("% e % e\n", sqrtf(-0.0), sqrt_apx(-0.0)); // -0.000000e+00 -2.698557e+19
测试代码
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
float sqrt_apx(float f) {
const int result = 0x1fbb4000 + (*(int*) &f >> 1);
return *(float*) &result;
}
double error_value = 0.0;
double error_worst = 0.0;
double error_sum = 0.0;
unsigned long error_count = 0;
void sqrt_test(float f) {
if (f == 0) return;
volatile float y0 = sqrtf(f);
volatile float y1 = sqrt_apx(f);
double error = (1.0 * y1 - y0) / y0;
error = fabs(error);
if (error > error_worst) {
error_worst = error;
error_value = f;
}
error_sum += error;
error_count++;
}
void sqrt_tests(float f0, float f1) {
error_value = error_worst = error_sum = 0.0;
error_count = 0;
for (;;) {
sqrt_test(f0);
if (f0 == f1) break;
f0 = nextafterf(f0, f1);
}
printf("Worst:%e %.2f%%\n", error_value, error_worst*100.0);
printf("Average:%.2f%%\n", error_sum / error_count);
fflush(stdout);
}
int main() {
sqrt_tests(FLT_TRUE_MIN, FLT_MIN);
sqrt_tests(FLT_MIN, FLT_MAX);
return 0;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
浮动sqrt_apx(浮动f){
常量int result=0x1fbb4000+(*(int*)&f>>1);
返回*(浮动*)和结果;
}
双误差_值=0.0;
双误差_最差=0.0;
双误差_和=0.0;
无符号长错误计数=0;
无效sqrt_试验(浮球f){
如果(f==0)返回;
挥发性浮点数y0=sqrtf(f);
挥发性浮点数y1=sqrt_apx(f);
双误差=(1.0*y1-y0)/y0;
误差=晶圆厂(误差);
如果(错误>错误\u最差){
错误\最坏=错误;
误差_值=f;
}
错误_sum+=错误;
错误计数++;
}
无效sqrt_测试(浮点数f0、浮点数f1){
错误值=错误最差值=错误总和=0.0;
错误计数=0;
对于(;;){
sqrt_检验(f0);
如果(f0==f1)中断;
f0=nextafterf(f0,f1);
}
printf(“最差:%e%.2f%%\n”,错误值,错误最差*100.0);
printf(“平均值:%.2f%\n”,误差总和/误差计数);
fflush(stdout);
}
int main(){
sqrt_测试(FLT_真实_最小值、FLT_最小值);
sqrt_测试(最小飞行时间,最大飞行时间);
返回0;
}
请参见奥利弗·查尔斯沃思(Oliver Charlesworth)对这一方法几乎奏效的原因的解释。我正在处理评论中提出的一个问题
由于一些人已经指出了它的不可移植性,这里有一些方法可以让它更可移植,或者至少让编译器告诉你它是否不起作用
首先,C++允许您在编译时检查<代码> STD::MultIICION::ISSIEC55 9/<代码>,例如在<代码> StistaSytRe><代码>中。您还可以检查
sizeof(int)==sizeof(float)
,如果int
为64位,这将不为真,但您真正想做的是使用uint32\u t
,如果它存在,它的宽度将始终正好为32位,将具有定义良好的移位和溢出行为,如果您的怪异体系结构没有这样的整型,则会导致编译错误。无论哪种方式,您都应该static\u assert()
确保类型具有相同的大小。静态断言没有运行时开销,如果可能的话,您应该始终以这种方式检查前提条件
不幸的是,将float
中的位转换为uint32_t
和移位是否为大端、小端或两者都不是的测试不能作为编译时常量表达式进行计算。在这里,我将运行时检查放在依赖于它的代码部分,但是您可能希望将其放在初始化中,并执行一次。实际上,gcc和clang都可以在编译时优化这个测试
您不想使用不安全的指针强制转换,我在现实世界中工作过的一些系统可能会因总线错误而导致程序崩溃。转换对象表示的最大可移植方法是使用memcpy()
。在下面的示例中,我使用一个联合
键入pun,它可以在任何实际存在的实现上工作。(语言律师反对它,但没有一个成功的编译器会默默地破坏这么多遗留代码。)如果必须进行指针转换(见下文),则有alignas()
。但无论您如何操作,结果都将由实现定义,这就是为什么我们检查转换和移动测试值的结果
无论如何,你不可能用我
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <vector>
using std::cout;
using std::endl;
using std::size_t;
using std::sqrt;
using std::uint32_t;
template <typename T, typename U>
inline T reinterpret(const U x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it reads an inactive union member.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
union tu_pun {
U u = U();
T t;
};
const tu_pun pun{x};
return pun.t;
}
constexpr float source = -0.1F;
constexpr uint32_t target = 0x5ee66666UL;
const uint32_t after_rshift = reinterpret<uint32_t,float>(source) >> 1U;
const bool is_little_endian = after_rshift == target;
float est_sqrt(const float x)
/* A fast approximation of sqrt(x) that works less well for subnormal numbers.
*/
{
static_assert( std::numeric_limits<float>::is_iec559, "" );
assert(is_little_endian); // Could provide alternative big-endian code.
/* The algorithm relies on the bit representation of normal IEEE floats, so
* a subnormal number as input might be considered a domain error as well?
*/
if ( std::isless(x, 0.0F) || !std::isfinite(x) )
return std::numeric_limits<float>::signaling_NaN();
constexpr uint32_t magic_number = 0x1fbb4000UL;
const uint32_t raw_bits = reinterpret<uint32_t,float>(x);
const uint32_t rejiggered_bits = (raw_bits >> 1U) + magic_number;
return reinterpret<float,uint32_t>(rejiggered_bits);
}
int main(void)
{
static const std::vector<float> test_values{
4.0F, 0.01F, 0.0F, 5e20F, 5e-20F, 1.262738e-38F };
for ( const float& x : test_values ) {
const double gold_standard = sqrt((double)x);
const double estimate = est_sqrt(x);
const double error = estimate - gold_standard;
cout << "The error for (" << estimate << " - " << gold_standard << ") is "
<< error;
if ( gold_standard != 0.0 && std::isfinite(gold_standard) ) {
const double error_pct = error/gold_standard * 100.0;
cout << " (" << error_pct << "%).";
} else
cout << '.';
cout << endl;
}
return EXIT_SUCCESS;
}
#include <cassert>
#include <cstdint>
#include <cstring>
using std::memcpy;
using std::uint32_t;
template <typename T, typename U> inline T reinterpret(const U &x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it modifies a variable.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
T temp;
memcpy( &temp, &x, sizeof(T) );
return temp;
}
constexpr float source = -0.1F;
constexpr uint32_t target = 0x5ee66666UL;
const uint32_t after_rshift = reinterpret<uint32_t,float>(source) >> 1U;
extern const bool is_little_endian = after_rshift == target;
#include <cassert>
template <typename T, typename U> inline T reinterpret(const U x)
/* Reinterprets the bits of x as a T. Cannot be constexpr
* in C++14 because it uses reinterpret_cast.
*/
{
static_assert( sizeof(T)==sizeof(U), "" );
const U temp alignas(T) alignas(U) = x;
return *reinterpret_cast<const T*>(&temp);
}