首页 > 系统相关 >浮点数在内存中的存储

浮点数在内存中的存储

时间:2024-09-26 18:21:14浏览次数:3  
标签:存储 1.0 有效数字 浮点数 二进制 内存 0000

引入

        首先来看一下以下这段代码:

int main() 
{
	int n = 1;
	float* pf = (float*)&n;

	printf("%d\n", n);
	printf("%f\n", *pf);

	*pf = 1.0;

	printf("%d\n", n);
	printf("%f\n", *pf);
	
	return 0;
}

        这里大多数人可能会认为会输出四个1,而事实真的是这样吗?

        可以观察到中间的两个数与我们现象有这很大的差异,第一个与最后一个数与我们想象的一样,由此我们可以得到一个结论:整形与浮点型在内存中的存储的方式是不一样的

一、浮点数在内存中的存储 

        根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点型数 V 可以表示成下面的形式:

   V = (-1) ^ S * M + 2^E

   S表示符号位,当s = 0,V为正数,当s = 1,V为负数。

   阶码部分(E)(指数部分),2^E(表示指数位)

   M表示有效数字,大于等于1,小于2, 浮点数的精度就是由尾数来决定的

        举例:

        十进制的 2.5 ,二进制表示为 10.1 ,写成科学计术法为 1.01 * 2^1

        按照 V = (-1) ^ S * M + 2^E 的格式,S = 0,M = 1.01,E = 1,写为 (-1) ^ 0 * 1.01 + 2^1

        十进制的 5.5 ,二进制表示为 101.1 ,写成科学计术法为 1.011 * 2^2

        按照 V = (-1) ^ S * M + 2^E 的格式,S = 0,M = 1.011,E = 2,写为 (-1) ^ 0 * 1.011 + 2^2

        十进制的 -7.5 ,二进制表示为 -111.1 ,写成科学计术法为 1.111 * 2^2

        按照 V = (-1) ^ S * M + 2^E 的格式,S = 1,M = 1.111,E = 2,写为 (-1) ^ 1 * 1.111 + 2^2

        国际标准IEEE(电气和电子工程协会)754规定:

        对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

        对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

补充:

        我们要明确一点浮点数在内存中是无法精确保存的

int main()
{
    //我们要明确一点浮点数在内存中是无法精确保存的
    if (0.1 + 0.2 == 0.3)
    {
        printf("1");
    }
    else
    {
        printf("2");
    }
    //输出2
    return 0;
}

二、浮点数的存入 

        国际标准IEEE(电气和电子工程协会) 754 对有效数字M和指数E,还有⼀些特别规定:

        数字M,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的小数部分

        ⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的⽬的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字

        指数E 规定E为⼀个⽆符号整数,这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047

        我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023

        比如 (-1) ^ 1 * 1.111 + 2^2 的E是2,所以保存成32位浮点数时,必须保存成2+127=129,即10000001,保存成64位浮点数时,必须保存成2+1023=1025,即010000000001   

     

int main()
{
    float num = 5.5;
    int a = 0;
    //十进制的 5.5 ,二进制表示为 101.1 ,写成科学计术法为 1.011 * 2 ^ 2
    //按照 V = (-1) ^ S * M + 2 ^ E 的格式,S = 0,M = 1.011,E = 2,写为(-1) ^ 0 * 1.011 + 2 ^ 2
    //内存中的存储为:0 10000001 01100000000000000000000
    //转换为十六进制:0x40 b0 00 00 
    return 0;
}

三、浮点数的读取 

        指数E从内存中取出还可以再分成三种情况:

        1、E不全为0或不全为1(与存入一样)

        浮点数就采⽤,指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1

int main()
{
    float num = 0.5;
    int a = 0;
    //十进制的 0.5 ,二进制表示为 0.5 ,写成科学计术法为 1.0 * 2 ^ -1
    //按照 V = (-1) ^ S * M + 2 ^ E 的格式,S = 0,M = 1.0,E = -1,写为(-1) ^ 0 * 1.0 + 2 ^ -1
    //阶码为 -1+127(中间值)=126,表⽰为 01111110
    //尾数1.0去掉整数部分为0,补⻬0到23位
    //内存中的存储为:0 01111110 00000000000000000000000
    //转换为十六进制:0x3f 00 00 00 
    return 0;
}

 

2、E全为0 

        浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,无限接近于0的很⼩的数字

0 00000000 00100000000000000000000 

3、E全为1

        如果有效数字M全为1,表⽰±⽆穷⼤(正负取决于符号位s) 

0 11111111 001000000000000000000000

题目解析 

        现在我们回到上面的习题

      1、第二个打印为什么不是1.0,而是0.0?

        在运行第二个打印时1是以整数的形式,存放到内存中的,得到的二进制序列为:

        0000 0000 0000 0000 0000 0000 0000 0001

        ⾸先,将 1 的⼆进制序列按照浮点数的形式拆分,得到第⼀位符号位s=0,后⾯8位的指数E=00000000 ,最后23位的有效数字M=000 0000 0000 0000 0000 0001 

        由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:

        V=(-1)^0 × 0.00000000000000000000001×2^(-126)=1.1×2^(-149)

        显然,V是⼀个很⼩的接近于0的正数,所以⽤⼗进制⼩数表⽰就是0.000000

      2、第三个打印为什么不是1.0,而是一个很大的数?

        在运行第三个打印时1是以浮点数的形式,存放到内存中的,得到的二进制序列为:

        0011 1111 1000 0000 0000 0000 0000 0000 

        十进制的 1.0 ,二进制表示为 1.0 ,写成科学计术法为 1.0 * 2^0

        按照 V = (-1) ^ S * M + 2^E 的格式,S = 0,M = 1.0,E = 0,写为 (-1) ^ 0 * 1.0 + 2^0

        阶码为 0+127(中间值)=127,表⽰为 01111111 ,尾数1.0去掉整数部分为0,补⻬0到23位

        将 0011 1111 1000 0000 0000 0000 0000 0000 二进制序列转换为十进制就是我们在屏幕上打印的数字

标签:存储,1.0,有效数字,浮点数,二进制,内存,0000
From: https://blog.csdn.net/LVZHUO_2022/article/details/142568069

相关文章

  • 对象存储比云硬盘更合适的场景
    对象存储和云硬盘是两种不同的云存储解决方案,它们在设计、性能和用途上各有特点。以下是一些对象存储比云硬盘更合适的场景:1.海量数据存储对象存储适合存储大量非结构化数据,如图像、视频、音频文件和大型文档,因为它可以横向扩展以容纳无限多的对象。2.数据备份和归档对象存储通常......
  • 对象存储和传统存储的区别是什么?
    对象存储和传统存储(主要包括块存储和文件存储)在数据管理、访问方式、扩展能力、架构设计和适用场景等方面存在显著差异。以下是对象存储和传统存储之间的一些主要区别:1.数据管理对象存储:以对象为单位管理数据,每个对象包含数据本身和一组元数据。元数据描述了对象的各种属性,如创建......
  • VB.net(C#同理)使用 ServiceStack.Redis 二进制存储、读取图像
    搜索了一下,网上似乎没有相关的内容,于是把自己探索的经验写一下。'安装提示:首先需要把当前的目标框架设置为.NetFramwork4.5。'方法一:复制ebay订单里的DLL\ServiceStack.Redis(整个文件夹),自行添加引用(4个dll)'方法二:使用Nuget安装servicestack.redis,选择5.0版本PublicClas......
  • Token: 数据库、存储系统和API安全的应用
    一.TokenToken是一种常见的计算机术语,它在不同的上下文中有不同的含义。在身份验证和授权的上下文中,Token通常指的是服务端生成的一串字符串,作为客户端进行请求的一个令牌。当用户登录后,服务器会生成一个Token并返回给客户端,客户端在后续的请求中携带这个Token,以此来验证用户......
  • 将一个表中的数据循环插入另外一个表中的mysql的存储过程
    DELIMITER$$CREATEPROCEDUREInsertDataIntoSysDictData()BEGIN--声明变量DECLAREdoneINTDEFAULTFALSE;DECLAREattribute_nameVARCHAR(255);DECLAREapply_modeVARCHAR(255); DECLAREcounter1INTDEFAULT0;--游标声明DECLAREc......
  • 存储服务器的 RAID 级别是什么意思?
    RAID(独立磁盘冗余阵列)是一种将多个物理硬盘组合成一个逻辑单元的技术,用以提高数据存储的性能和可靠性。RAID级别指的是这些硬盘组合的不同方式,每个级别都有其独特的性能、可靠性和成本特点。以下是几种常见的RAID级别及其含义:RAID0(条带化)性能:提高数据读写速度,因为数据被分散存储在......
  • 深入理解并发原子性、可见性、有序性与JMM内存模型
    1.并发三大特性并发编程Bug的源头:原子性、可见性和有序性问题1.1原子性一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。不采取任何的原子性保障措施的自增操作并不是......
  • 巧用时间换空间:解读 ArcGraph 如何灵活应对有限内存下的图分析
    导读:ArcGraph是一款云原生架构、存查分析一体化的分布式图数据库。本文将详细解读ArcGraph如何灵活应对有限内存下的图分析。01引言在图分析技术广泛应用的当下,学术界和各大图数据库厂商热衷于提升图分析技术的高性能指标。然而,追求高性能计算的过程中,常采用“以空间换时间......
  • Cpp内存管理(7)
    文章目录前言一、C/C++内存区域划分二、C/C++动态内存管理C语言动态内存管理C++动态内存管理对于内置类型对于自定义类型三、new和delete的底层实现四、new和delete的实现原理五、定位new六、malloc/free和new/delete的区别总结前言  软件开发过程中,内存管理的......
  • prometheus学习笔记之prometheus存储系统
    一、prometheus本地存储系统1.本地存储架构默认情况下,prometheus将采集到的数据存储在本地的TSDB数据库中,路径默认为prometheus安装目录的data目录,数据写入过程为先把数据写入wal日志并放在内存,然后2小时后将内存数据保存至一个新的block块,同时再把新采集的数据......