热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

浮点数在内存中的表示

1.浮点数的二进制格式.浮点数的二进制格式如下,分为3个部分,即sign(符号位),exponent(指数位),以及significand(有效数位).如下图所示:single

 

 

        1. 浮点数的二进制格式.

        浮点数的二进制格式如下, 分为 3 个部分, 即 sign (符号位), exponent (指数位), 以及 significand (有效数位). 如下图所示:

        single-precision floating point(单精度浮点数), 4 字节, 共 32 位:

         31            23   22                               0

        +-----------------------------------------------------+

        | s | exp(biased) |          significand              |

        +-----------------------------------------------------+

        double-precision floating point(双精度浮点数), 8 字节, 共 64 位:

         63              52   51                                                        0

        +--------------------------------------------------------------------------------+

        | s |  exp(biased)  |                       significand                          |

        +--------------------------------------------------------------------------------+

        最高位为符号位, 单精度浮点数的指数位 8 位, 有效数位 23 位; 双精度浮点数的指数位 11 位, 有效数位 52 位.

 

        二进制浮点数采用类似十进制小数的科学计数法来表示(例如 3140.0 可以表示为 3.14 * 10 ^ 3), 即将任意浮点数都表示为 1.xxx~xxx * 2 ^ exponent 这种形式. ‘x‘ 代表 0 或者 1, 毫无疑问 exponent 部分也只能是二进制形式的. 由于 1.xxx~xxx 中的 1 是固定的, 因此没有必要将其在 significand 中表示出来, 所以 significand 中只包含了 xxx~xxx 部分, 没有包含那个固定的 1, 即这个 1 是隐式存在的(或被称为 J-bit). 所以浮点数的表示式子也可以写作

        J-bit.significand * 2 ^ exponent.

        还要注意, 在图示中写的是 exp(biased), 而浮点数表示式子中写的是 exponent, 因为实际存储在内存中的指数 exp(biased) 是经过偏置(biase)后的 exponent.

        在 x86 / amd64 中, 还有一种 double extended-precision floating point(扩展双精度浮点数), 10 字节, 共 80 位. 但是其 J-bit 是显式的(该位必须为 1, 否则为 unsupported 编码):

         79                  64  63  62                                                              0

        +---------------------------------------------------------------------------------------------+

        | s |    exp(biased)   | J |                    significand                                   |

        +---------------------------------------------------------------------------------------------+

        这是因为 x86 构架的浮点协处理器 x87 FPU 硬件上使用扩展双精度类型(就是说这个浮点协处理器的寄存器都是 10 个字节 80 位的). 如果编译器使用该协处理器来处理浮点数的话, 则编译器要负责将所有浮点数转换为扩展双精度, 调用 x87 FPU 指令进行浮点处理, 完事后再转换回来(略扯淡, 但是事实). 当然编译器也可以不使用 x87 FPU 用软件模拟浮点处理, 不然你以为 GMP (GNU Multiple-Precision, GNU 大数库, 号称地球第一快) 库是用来干嘛的, gcc 干嘛要依赖它.

 

        2. 将十进制小数转换为二进制表示

 

        先转换整数部分, 再转换小数部分(废话). 比如 3.14, 转换整数部分得到 11, 接下来转换小数部分(乘 2 取整):

                0.14 * 2 = 0.28, 0

                0.28 * 2 = 0.56, 0

                0.56 * 2 = 1.12, 1

                0.12 * 2 = 0.24, 0

                0.24 * 2 = 0.48, 0

                ...

        所以十进制 3.14 转换为二进制表示就是 11 . 00100...

        这种转换手工算吃力不讨好, 干嘛不随便写个短程序解决它(当然只是为了随便看看, 不用写得很高大上):

bubuko.com,布布扣bubuko.com,布布扣
 1 #include 
 2 
 3 void foo(int n)
 4 {
 5     int q, r;
 6 
 7     if (n <= 0)
 8         return;
 9 
10     r = n % 2;
11     q = n / 2;
12 
13     foo(q);
14 
15     printf("%c", r + 0);
16 }
17 
18 #define EPSILON 0.0000001
19 
20 int main(void)
21 {
22     double d;
23     int inte_part;
24     double frac_part;
25 
26     printf("input d: ");
27     scanf("%lf", &d);
28 
29     inte_part = d;
30 
31     foo(inte_part);
32     printf(" . ");
33 
34     frac_part = d - inte_part;
35     while (!(-EPSILON  EPSILON))
36     {
37         inte_part = frac_part * 2;    
38         frac_part = frac_part * 2 - inte_part;
39     
40         printf("%d", inte_part);
41     }
42 
43     putchar(\n);
44 
45     return 0;
46 }
View Code

 

        代码如上. 编译这个程序并运行, 例如输入 3.14 (注意不要输入负数. 输入负数的话, 代码中可以判断一下, 比如 if (d <0) d = -d; 并先输出一个负号即可. 我忘了这档子事了), 我们得到其二进制表示为:

        11 . 001000111101011100001010001111010111000010100011111...

        嗯, 程序中有设定 EPSILON, 所以这个转换可能没完, 不过够用就行.

 

        3. 查看 C 语言 float 类型变量的内存表示

 

        这个不难. 不过仍然有一点需要注意. 类型决定对类型实例之上的操作是否合法, 对于 C 语言的 float 类型定义的实例对象来说, 不能直接对它进行位操作. 因此需要通过指针将该浮点对象一个字节一个字节地取出来再做位操作. 代码如下:

bubuko.com,布布扣bubuko.com,布布扣
 1 #include 
 2 
 3 int main(void)
 4 {
 5     float f;
 6     char *p = NULL;
 7     char bits[32];
 8 
 9     printf("f = : ");
10     scanf("%f", &f);
11 
12     p = (char *)&f;
13 
14     int i, j;
15     for (i = 0; i <4; i++)
16     {
17         for (j = 0; j <8; j++)    
18         {
19             (*p & (0x1 << j)) 
20                 ? (bits[i * 8 + j] = 1) 
21                 : (bits[i * 8 + j] = 0);
22         }
23         p++;
24     }
25 
26     // 从 0 --> 31 位顺序打印.
27     for (i = 0; i <32; i++)
28     {
29         putchar(bits[i]);    
30     }
31     putchar(\n);
32 
33     // 分别打印符号位, 指数, 有效数位.
34     printf("1  bit,        sign: %c\n", bits[31]);
35     printf("8  bit, exp(biased): ");
36     for (i = 30; i > 22; i--)
37         putchar(bits[i]);
38     putchar(\n);
39     printf("23 bit, significand: ");
40     for (i = 22; i >= 0; i--)
41         putchar(bits[i]);
42     putchar(\n);
43 
44     return 0;
45 }
View Code

 

        运行, 输入 3.14, 得到如下运行结果:

        f = : 3.14
        11000011101011110001001000000010        // 注意, 这是按 0 --> 31 顺序打印的. 符号位是第 31 位, 0
        1  bit,        sign: 0
        8  bit, exp(biased): 10000000
        23 bit, significand: 10010001111010111000011

        这个运行结果首先是将浮点数的内存表示从 0 --> 31 由低位到高位按位打印(由于在 x86 平台上, 代码是按照小端格式写的). 下面的 sign, exp, significand 依次为内存中的符号, 指数部分和有效数位部分.

        对比一下将十进制 3.14 转换为二进制表示的结果: 11 . 001000111101011100001010001111010111000010100011111... 这个用浮点表示式子应该写为:

        1. 1001 0001 1110 1011 1000 0101... * 2 ^ 1

        嗯, 符号位(第 31 位)是 0, 因为是正数所以符号位是 0, 这个没有问题.

        有效数位呢? 1001 0001 1110 1011 1000 011 这个也没有问题(J-bit没有在sigificand中), 最后的 011 是由 0101 舍入 (round) 的, 因为单精度浮点有效数位只有 23 位啊, 摊手.

        指数位呢? 不应该是 00000001 (1) 吗, 为什么是 10000000 (128)?

        之前说过, 内存中的指数是要经过偏置的. 对于单精度浮点数来说, 这个偏置值是 127, 即(2 ^ 8 / 2 - 1). 所以指数偏置后 1 + 127 == 128. 为什么要经过偏置呢? 因为 IEEE 754 考虑, 如果一个数指数大, 那么它肯定比指数小的浮点数要大(J-bit固定为 1). 在比较指数大小这种事情上, 只用正数(负数 -127 加上偏置值 127 后为 0). 而当指数位为全 1(-128) 的情况下用来表示无穷大. 所以就有了这个偏置值了. 同理双精度浮点数的偏置值是 2 ^ 11 / 2 - 1 == 1023.

        关于 IEEE 754, 还有浮点舍入的问题有空了再写罢.

 

        以上就是一个 float 类型实例在内存中的表示了. 对于 double 类型, 方法是一样的, 不过在将十进制转换为二进制的表示上要通过 C 代码得到准确的精度输出的话就那个呵呵呵了. 嗯, 还有比如给函数传递浮点参数时编译器怎么处理之类的问题, 困了...~.

 

 

浮点数在内存中的表示,布布扣,bubuko.com

浮点数在内存中的表示


推荐阅读
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文介绍了一种解析GRE报文长度的方法,通过分析GRE报文头中的标志位来计算报文长度。具体实现步骤包括获取GRE报文头指针、提取标志位、计算报文长度等。该方法可以帮助用户准确地获取GRE报文的长度信息。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
author-avatar
黄霖hy
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有