浮点数在内存中的表示实例(IEEE-754)
浮点数在内存中的表示
例1:float型浮点数125.5转化成32位二进制浮点数
125.5的二进制码为1111101.1,写成二进制的科学计数为:1.111101*2^6(因为科学计数法“整数”部分大于1,在二进 制中,“整数”部分只能恒为1)即向左移6位,则e=6,则E=e+127=133,而E的二进制码为10000101,而1.111101把“整数”部 分去除1之后为111101,之后补0,共23b,形成了阶码。
所以125.5的32位二进制浮点数为
0 10000101 11110100000000000000000
c语言中的浮点数在内存中的表示(VC++编译器中):
char:1个字节
short:2个字节
int:4字节
long:4字节
float:4字节(单精度)
double:8字节(双精度)
参考:http://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html
无论是单精度还是双精度在存储中都分为三个部分:
- 符号位(Sign) : 0代表正,1代表为负
- 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
- 尾数部分(Mantissa):尾数部分
其中float的存储方式如下图所示:
而双精度的存储方式为:
R32.24和R64.53的存储方式都是用科学计数法来存储数据的,比如8.25用十进制的科学计数法表示就为:8.25*,而120.5可以表示为:1.205*
。而我们计算机根本不认识十进制的数据,他只认识0,1,所以在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示,8.25用二进制表示可表示为1000.01(具体方法见这)。120.5用二进制表示为:1110110.1用二进制的科学计数法表示1000.01可以表示为1.0001*
,1110110.1可以表示为1.1101101*
,任何一个数都的科学计数法表示都为1.xxx*
, 尾数部分就可以表示为xxxx,由于第一位都是1,可以将小数点前面的1省略,所以23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里,那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位,而对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127-128了, 所以指数部分的存储采用移位存储,存储的数据为原数据+127,下面就看看8.25和120.5在内存中真正的存储方式。
首先看下8.25,用二进制的科学计数法表示为:1.0001*
按照上面的存储方式,符号位为:0,表示为正,指数位为:3+127=130 ,尾数部分为0001,故8.25的存储方式如下图所示:
而单精度浮点数120.5的存储方式如下图所示:
那么如果给出内存中一段数据,并且告诉你是单精度存储的话,你如何知道该数据的十进制数值呢?其实就是对上面的反推过程,比如给出如下内存数据:0100001011101101000000000000,首先我们现将该数据分段,0 1000 0101 110 1101 0000 0000 0000 0000,在内存中的存储就为下图所示:
根据我们的计算方式,可以计算出,这样一组数据表示为:1.1101101*=120.5
而双精度浮点数的存储和单精度的存储大同小异,不同的是指数部分(11位)和尾数部分的位数(52位),并且对于指数部分,双精度采用:原数据+1023。所以这里不再详细的介绍双精度的存储方式了,只将120.5的最后存储方式图给出,大家可以仔细想想为何是这样子的
下面我就这个基础知识点来解决一个我们的一个疑惑,请看下面一段程序,注意观察输出结果
float f = 2.2f;
double d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
f = 2.25f;
d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
可 能输出的结果让大家疑惑不解,单精度的2.2转换为双精度后,精确到小数点后13位后变为了2.2000000476837,而单精度的 2.25转换为双精度后,变为了2.2500000000000,为何2.2在转换后的数值更改了而2.25却没有更改呢?很奇怪吧?其实通过上面关于两 种存储结果的介绍,我们已经大概能找到答案。首先我们看看2.25的单精度存储方式,很简单 0 1000 0001 001 0000 0000 0000 0000 0000,而2.25的双精度表示为:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,这样2.25在进行强制转换的时候,数值是不会变的,而我们再看看2.2呢,2.2用科学计数法表示应该为:将十进制的小数转换为二进制的小数 的方法为将小数*2,取整数部分,所以0.282=0.4,所以二进制小数第一位为0.4的整数部分0,0.4×2=0.8,第二位为 0,0.8*2=1.6,第三位为1,0.6×2 = 1.2,第四位为1,0.2*2=0.4,第五位为0,这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011... ,对于单精度数据来说,尾数只能表示24bit的精度,所以2.2的float存储为:
但 是这样存储方式,换算成十进制的值,却不会是2.2的,应为十进制在转换为二进制的时候可能会不准确,如2.2,而double类型的数 据也存在同样的问题,所以在浮点数表示中会产生些许的误差,在单精度转换为双精度的时候,也会存在误差的问题,对于能够用二进制表示的十进制数据,如 2.25,这个误差就会不存在,所以会出现上面比较奇怪的输出结果。
参考:http://www.kuqin.com/language/20100606/85209.html
单精度浮点数: 1位符号位 8位阶码位 23位尾数
双精度浮点数: 1位符号位 8位阶码位 52位尾数
实数在内存中以规范化的浮点数存放,包括数符、阶码、尾数。数的精度取决于尾数的位数。比如32位机上float型为23位 double型为52位。
单精度float型存储在内存中的大小为4个字节,即32位。
浮点表示的一般形式为:R=M*2^e (R:Real M:Mantissa尾数 e:exponent阶码)
把上面float的二进制可分成三部分:
x xxxxxxxx xxxxxxxxxxxxxxxxxxxxxxx
数符(1b) 阶码(8b) 尾数(23b)
double型的浮点数分别是:数符(1b)、阶码(8b)、尾数(52b)
数符sign:real的正负号 "+":0 "-":1
阶码e:e=E-127(double型中e=E-1023) e为正值说明这个浮点数向左移动了e位, e为负值说明这个浮点数向右移动了e位。127=2^7-1 1023=2^10-1
尾数M:有效数字位,这里是有效数字位的部分二进制码
例1:float型浮点数125.5转化成32位二进制浮点数
125.5的二进制码为1111101.1,写成二进制的科学计数为:1.111101*2^6(因为科学计数法“整数”部分大于1,在二进 制中,“整数”部分只能恒为1)即向左移6位,则e=6,则E=e+127=133,而E的二进制码为10000101,而1.111101把“整数”部 分去除1之后为111101,之后补0,共23b,形成了阶码。
所以125.5的32位二进制浮点数为
0 10000101 11110100000000000000000
例2:float型浮点数0.5转化成32位二进制浮点数
0.5的二进制码为0.1,写成二进制的科学计数为:1.0*2^(-1)即向右移1位,则e=-1,则E=e+127=126,而E的二进制码为01111110,而1.0把“整数”部分去除1之后为0,之后补0,形成了阶码。
所以0.5的32位二进制浮点数为
0 01111110 00000000000000000000000
double型浮点数类似。
例3:32位二进制浮点数为0 10000010 00010000000000000000000转化成十进制数浮点数
题中已给我们分了三部分,数符部分、阶码部分、尾数部分。
数符部分为0,则代表此数为正数;阶码部分为10000010,则E=130,则e=E-127=3,则说明其向左移了3位,0001加上“整数”部分的1之后,为1.0001。则原二进制数为1000.1=十进制8.5,或R=1.0001*2^3=8.5
其中很多计算类似。可举一反三!
转自<http://blog.****.net/borefo/article/details/4620964>
今天看到一段代码,如下:请写出它的输出结果
#include<iostream>
using namespace std;
int main(void) {
float a = 1.0f;
cout << &a << endl;
cout << (int)&a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl;
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl;
return 0;
}
看到这段代码,一些概念性的东西一下就搞混了,(int&)a是什么含义呢?让我来看看输出结果:
0013FF60
1310560
1065353216
false
0
0
true
(int)&a 把a的地址强制转换成整型
(int&)a 把a强制转换成整形引用类型,相当于是某个整形引用,引向了a所在的32位内存区域,并将这32位当做一个整数。
(int)a a在内存中的值转换成int类型
对于浮点数1.0,根据IEEE754的浮点数存储格式表示为3f800000(0011 1111 1000 0000 0000 0000 0000 0000),(int&)a把3f800000当做int型输出,所以结果为1065353216。
这里对float和double存储格式介绍一下:
C/C++的浮点数据类型有float和double两种。
类型float大小为4字节,即32位,内存中的存储方式如下:
高地址<-------------------------------------->低地址
| 符号位 | 指数 | 尾数 |
| 1 bit | 8 bit | 23 bit |
31<------>30<--------->22<---------------------->0
类型double大小为8字节,即64位,内存布局如下:
高地址<---------------------------------------->低地址
| 符号位 | 指数 | 尾数 |
| 1 bit | 11 bit | 52 bit |
63<------>62<------------>51<--------------------->0
符号位决定浮点数的正负,0表示正,1表示负。
指数和尾数均从浮点数的二进制科学计数形式中获取。
如,十进制浮点数2.5的二进制形式为10.1,转换为科学计数法形式为(1.01)*(21),由此可知指数为1,尾数(即科学计数法的小数部分)为01。
根据浮点数的存储标准(IEEE制定),float类型指数的起始数为127(二进制0111 1111),double类型指数的起始数为1023(二进制011 1111 1111),在此基础上加指数,得到的就是内存中指数的表示形式。尾数则直接填入,如果空间多余则以0补齐,如果空间不够则0舍1入。所以float和double类型分别表示的2.5如下(二进制):
float
|符号位| 指数 | 尾数
| 0 | 1000 0000 | 010 0000 0000 0000 0000 0000
double
|符号位| 指数 | 尾数
| 0 | 100 0000 0000 | 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
浮点数2.5可以用二进制小数准确表示(2.5=1*(21)+0*(20)+1*(2-1)),但很多小数不可以准确表示,其二进制形式的小数部分会无限循环,如浮点数-1.2表示如下(二进制):
float
|符号位| 指数 | 尾数
| 1 | 0111 1111 | 0011 0011 0011 0011 0011 010
double
|符号位| 指数 | 尾数
| 1 | 011 1111 1111 | 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
由于对无限循环尾数的截取遵循0舍1入,尾数的第21~24位为0011,第53~56位为0011,而float尾数容量为23位,double尾数容量为52位,所以,float形式的最后三位因进位而成010,double形式则没有进位发生。
类型float和double通过==,>,<等比较不会引起编译错误,但是非常可能得到错误的结果。这是因为它们的内存分布不同,不可以直接比较。正确的方法是转换为同一类型后比较两者差值,如果结果小于规定的小值,则视为相等。
ps:
1) IEEE浮点数标准:
4字节浮点数:1位符号位,8位阶数(基数为127的移码),23位尾数;
8字节浮点数:1位符号位,11位阶数(基数为1023的移码),52位尾数
2 ) 在VC中: float数值范围约在 -10e38~10e38,并提供7位有效数字位,绝对值小于10e38地数被处理成零值 double数值范围约在-10e308~10e308,并提供15~16位有效数字,绝对值小于10e308地数被处理成零值