1/sqrt(x)快速神奇算法来龙去脉,浮点数二进制表示和int二进制输出

本文详细介绍了网络上著名的平方根倒数快速算法,并通过代码实现进行了解析。探讨了算法的历史背景及其数学原理,同时展示了如何在内存层面进行float和int之间的转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天研究了一天网络上著名的平方根倒数快速神奇算法。

代码如下:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;
    x2 = number * 0.5F;
    y   = number;
    i   = * ( long * ) &y;   // evil floating point bit level hacking
    i   = 0x5f3759df - ( i >> 1 ); // what the fuck?
    y   = * ( float * ) &i;
    y   = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
    // y   = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

    #ifndef Q3_VM
    #ifdef __linux__
         assert( !isnan(y) ); // bk010122 - FPE?
    #endif
    #endif
    return y;
}  

简洁版:

float InvSqrt (float x) {  
     float xhalf = 0.5f * x;  
     long i = *(long*)&x;  
     i = 0x5f3759df - (i >> 1);  
     x = *(float *)&i;  
     x = x * (1.5f - xhalf * x * x);  
     return x;  
}  

我先是看了这个帖子,里面讲了一些来龙去脉,但是有些和Wiki上的信息冲突:

https://blog.youkuaiyun.com/qq_26499321/article/details/73724763

对于这个算法的历史,我比较倾向于中文wiki的说法:

https://zh.wikipedia.org/wiki/%E5%B9%B3%E6%96%B9%E6%A0%B9%E5%80%92%E6%95%B0%E9%80%9F%E7%AE%97%E6%B3%95#cite_note-Beyond3D-1

关于它的整个算法流程描述和数学分析,下面这篇博客译文说得更清楚:

https://blog.youkuaiyun.com/zdy0_2004/article/details/52477640

然后对于里面的这个转换:

i   = * ( long * ) &y;   // evil floating point bit level hacking
y   = * ( float * ) &i;

涉及到 int (或者long, 在32位编译器下都是32bit的长度)和float的表示方法,然后去阅读了下面这个博客,说得比较清楚:

浮点数的二进制表示 http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html

上述博客开篇里面提到一个程序, 我做了一些修改,用Mac sublime text 编译了一下

(注:在网上找了一个二进制输出函数)

/*
* @Author: simonliu
* @Date:   2018-04-25 13:56:38
* @Last Modified by:   simonliu
* @Last Modified time: 2018-04-26 11:58:21
*/
#include <stdio.h>
#include <stdlib.h>


void print_2(int val2);

int main()
{

    int num=9;
    float *pFloat = (float*)#
    printf("num的值是:%d\n",num);
    printf("*pFloat的值是:%f\n", *pFloat);
    printf("内存的二进制值是:\n");
    print_2(num);
    printf("\n---------------\n");
    *pFloat = 9.0;
    printf("num的值是:%d\n",num);
    printf("*pFloat的值是:%f\n", *pFloat);
    printf("内存的二进制值是:");
    print_2(num);

    		return 0;

}

void print_2(int val2)
{
unsigned char *p = (unsigned char*)&val2 + 3; //从低位到高位,低端字节计算机
for(int k = 0; k <= 3; k++)
{
int val2 = *(p-k);
for (int i = 7; i >= 0; i--)
{
if(val2 & (1 << i))
printf("1");
else
printf("0");
}
printf(" ");
}
}

延伸问题:一段内存的二进制的输出

printf()可以直接打印十进制,八进制,十六进制,输出控制符分别为%d, %o, %x, 但是它不支持二进制输出。

有些编译器支持 stdlib.h的 itoa()函数,但是Mac自带的clang编译器不支持。所以上例中使用了一个网上找来的二进制输出函数(print_2)。

关于二进制输出,还有另外一篇博客写了一个递归取模的函数:

https://blog.youkuaiyun.com/xzongyuan/article/details/29562371

我不喜欢递归,所以自己写了一个按位操作取二进制的函数,完整代码如下

1.适合4个字节的int ,float类型变量。

2. 如果是int负数,输出的是补码而不是原码。

//
//  main.c
//  binOutput
//
//  Created by SimonLiu on 2018/5/2.
//  Copyright © 2018年 SimonLiu. All rights reserved.
//

#include <stdio.h>
#include "string.h"


int main()
{
    float num;

    printf("Enter float number:");
    scanf("%f",&num);

    int newNumber = *(int *)(&num); //把它看做整型
    
    int b = sizeof(num);
    printf("The number has %d bytes.\n",b);
    
    for (int i = (b*8)-1; i>=0; i--)
    {
        printf("%d",(newNumber&(1<<i))>>i);  
    }
    
    printf("\n");
    return 0;
}

下面这个算法是更早写的一个,略显复杂,也是针对4字节的int:

/*
 * @Author: simonliu
 * @Date:   2018-04-25 21:48:58
 * @Last Modified by:   simonliu
 * @Last Modified time: 2018-04-25 22:24:15
 */
#define __Mylog__
#ifdef __Mylog__
#define Mylog(format,...) printf("File: " __FILE__ ", Line: %05d: " format "/n", __LINE__, ##__VA_ARGS__)
#else
#define Mylog(format,...)
#endif
#include <stdio.h>
#include "string.h"


void dec2bin(int num);

int main()
{ int decimalNum;
    
    printf("Enter number in decimal:");
    scanf("%d",&decimalNum);
     Mylog("\n Mylog enabled...\n");
     Mylog("\n Decimal%d:",decimalNum);
    printf("binary format is : \n");
    dec2bin(decimalNum);
    printf("\n");
    return 0;
}

void dec2bin(int num)
{
    int i = 0;
    int buffer[32];
    memset(buffer, 0, sizeof(buffer));
    Mylog("\n num= %d, ",num);
    if (num < 0)
    {
        num &= ~(1 << 31); //首位清0,后面按照正数操作
        buffer[31] = 1;    //首位记录为1

    }
    Mylog("\n new num= %d, ",num);
    while (num >0)
    {

        if (num&1)  //与00000001 按位&, 即检查最后一位是否为1
        {
        buffer[i] = 1;
        Mylog("   buffer[%d] = %d\n",i,buffer[i]);
        }
        else
        {
        buffer[i] = 0;
         Mylog("   buffer[%d] = %d\n",i,buffer[i]);
        }
        Mylog("\nnum= %d, ",num);
        Mylog("i= %d\n",i);
        num = num >> 1 ;
        i ++;
        
    }
    for (int i = 31; i>=0; i--)
    {
        printf("%d",buffer[i]);
        if (i%4 == 0)
            printf(" ");
        
    }
    
}



<think>好的,我现在需要帮用户解答关于半精度、单精度双精度浮点数二进制表示形式,以及它们进行加减乘除、指数、根式、微积分对数运算的方法。首先,我需要确认用户的需求是什么。用户可能在进行数值计算、编程或者学习计算机组成原理时遇到这个问题,想深入了解浮点数的内部表示数学运算的实现方式。 首先,我应该回忆IEEE 754标准,这是浮点数表示的基础。半精度(16位)、单精度(32位)双精度(64位)的格式可能不同,需要分别说明它们的二进制结构,包括符号位、指数位尾数位的分布。例如,半精度的1位符号,5位指数,10位尾数;单精度是1+8+23,双精度1+11+52。这部分需要准确,不能出错。 接下来,关于运算方法。加减乘除这些基本运算需要分开说明。浮点数的加减法涉及到对阶、尾数运算、规格化等步骤,而乘除法则是指数相加/减,尾数相乘/除,然后处理规格化舍入。这里可能需要举例或者步骤分解,但用户可能更关注原理而非具体实现细节。 对于指数、根式、微积分对数运算,这些属于更高级的数学运算。可能需要解释这些运算如何转化为基本的浮点操作。例如,指数运算可能使用泰勒展开或查表法,根式运算可能转换为指数形式(如平方根1/2次方),微积分涉及数值积分方法如辛普森法则,而对数运算可能用换底公式或近似算法。需要注意的是,这些方法在不同精度下的实现可能不同,尤其是精度误差控制方面。 同时,用户提供的引用中有提到float、doubledecimal的区别,特别是精度范围。这可能提示用户对精度的影响感兴趣,所以在解释运算方法时,需要提到不同精度浮点数在运算中的误差累积问题,比如半精度在复杂运算中可能更快出现精度损失,而双精度更适合科学计算。 用户可能没有明确说明的是,他们可能在实际应用中遇到了精度问题,或者需要选择合适的数据类型进行优化。因此,在回答时应该结合不同精度的适用场景,比如半精度在机器学习中的使用,单精度在图形处理,双精度在科学计算等。 另外,生成相关问题时要考虑用户可能的后续疑问,例如不同精度浮点数的性能差异、如何选择精度类型、误差分析方法等。这些问题可以帮助用户深入理解并应用所学知识。 最后,需要确保回答结构清晰,分点说明二进制表示各类运算方法,语言准确且易于理解。同时,引用用户提供的资料中的相关内容,比如引用[1]中关于精度范围的说明,来支持解释不同浮点类型的特性。</think>### 浮点数二进制表示及运算方法详解 #### 一、浮点数二进制表示(IEEE 754标准) 1. **半精度(16位)** - 符号位:1位(最高位) - 指数位:5位(偏移值$15$) - 尾数位:10位(隐式前导1) - 范围:约$±6.1×10^{-5}$至$6.5×10^4$,精度约3-4位小数[^1] 2. **单精度(32位)** - 符号位:1位 - 指数位:8位(偏移值$127$) - 尾数位:23位 - 范围:约$±1.2×10^{-38}$至$3.4×10^{38}$,精度约7位小数 3. **双精度(64位)** - 符号位:1位 - 指数位:11位(偏移值$1023$) - 尾数位:52位 - 范围:约$±2.2×10^{-308}$至$1.8×10^{308}$,精度约15-17位小数[^1] #### 二、基本算术运算原理 1. **加减法** - 对阶操作:将小指数调整为与大指数对齐 - 尾数运算:对调整后的尾数进行加减 - 规格化:将结果调整为$1.xxx×2^E$形式 - 例:计算$1.1×2^3 + 1.01×2^1$需先对阶为$1.1×2^3 + 0.0101×2^3$ 2. **乘除法** - 指数运算:乘法相加/除法相减 - 尾数运算:定点数乘法/除法 - 规格化与舍入:处理溢出精度损失 #### 三、高级数学运算实现 1. **指数运算** - 泰勒展开:$$e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!}$$ - 硬件加速:现代CPU常内置FPU单元优化此类运算 2. **根式运算** - 牛顿迭代法:例如求平方根$\sqrt{a}$,迭代公式: $$x_{n+1} = \frac{1}{2}(x_n + \frac{a}{x_n})$$ 3. **微积分运算** - 数值积分:如辛普森法则 $$\int_a^b f(x)dx \approx \frac{b-a}{6}[f(a)+4f(\frac{a+b}{2})+f(b)]$$ 4. **对数运算** - 换底公式:$$\log_b x = \frac{\ln x}{\ln b}$$ - 查表法:预计算常用对数值加速运算 #### 四、精度对运算的影响 - 半精度在连续运算中误差积累明显,适合机器学习参数存储 - 单精度满足大多数图形渲染需求 - 双精度用于科学计算(如有限元分析)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值