赞
踩
目录
按IEEE754标准,32位浮点数(float)和64位浮点数(double)的标准格式如下:
f = (-1)^S * (1.M) *2^e 其中:e=E-127或1023
S:符号位, 0:表示正数, 1:表示负数
E:阶码,可以理解为将一个浮点数的整数部分转换成二进制之后, 然后将该二进制数移位成1.M的形式, E为移动成1.M所需的次数e+固定偏移。在IEEE754标准中将浮点数的指数真值e变成阶码E时,需要加上固定偏移:单精度固定偏移:127,双精度固定偏移:1023
单精度(float)中E=e+127(2^(8-1)-1),双精度(double)中E=e+1023(2^(11-1)-1)
M:位数, 即将浮点数的整数部分和小数部分转换成二进制之后,然后通过移位转换成1.M的形式之后的M值,是一个纯小数部分。
在IEEE754标准中,一个单精度的32位浮点数f32的真值表示为:
e=E-127, f32 = (-1)^S * (1.M) * 2^e
在IEEE754标准中,一个双精度的64位浮点数f64的真值表示为:
e=E-1023, f64 = (-1)^S * (1.M) * 2^e
其中尾数域所表示的值是1.M。由于规格化的浮点数的尾数域最左位(最高有效位)总是1,故这一位经常不予存储,而认为隐藏在小数点的左边,取数的时候在尾数前加1。单精度时:用23位字段可以存储24位有效数。双精度时:用52位字段可以存储53位有效数。
除2取余,商继续除2取余,直到商为0。读数:从最后一个余数读起,一直读到最前面的余数。
乘2取整,取完整数后剩余的小数部分继续乘2取整,一直取到小数部分为0为止。如果一直不为0,按照要求保留小数位数,根据保留位数后一位是0还是1取舍,是0舍去,是1入位。读数:从前面的整数读取到后面的整数部分。
例1:将11.375转换成IEEE754标准的32位浮点数的二进制存储格式。
解:整数:11
第一步:11 / 2 = 5余1
第二步:5 / 2 = 2余1
第三步:2 / 2 = 1余0
第四步:1 / 2 = 0余1
读数:(11)10 = (1011)2小数部分:0.375
第一步:0.375 * 2 = 0.750取整0,小数部分0.750
第二步:0.750 * 2 = 1.500取整1,小数部分0.500
第三步:0.500 * 2 = 1.000取整1,小数部分0
读数:小数部分:(0.375)10=(0.011)2综合:(11.375)10=(1011.011)2
转换尾数:(11.375)10=(1011.011)2 = 1.M = 1.011011 * 2 ^3
所以:M=01101100000000000000000,e=3, E=e+127=(130)10=(10000010)2
S=0:所以11.375在计算机中的存储格式为:0 10000010 01101100000000000000000
求取计算机内存存储的单精度浮点数【0 10000010 01101100000000000000000】的十进制
f32=(-1)^S*(1.M)*2^e = (-1)^0 * (1.01101100000000000000000) * 2^3 = 1 * (1011.011)2
=(11.375)10特别注意:这里是浮点数二进制计算,不要和十进制浮点数计算搞混,千万不要理解成1.011011 * 2^3 = 1.011011 * 8,这里的2^3是移位阶码e,不是十进制中的幂次方。
C语言验证:
void print_binary(void *addr, size_t size)
{
unsigned char* ptr = (unsigned char* )addr;
char out[256] = {0};
int len = 0;
printf("Hex: ");
for(size_t byte_idx = size; byte_idx > 0; --byte_idx)
{
unsigned char byte = *(ptr + byte_idx - 1);
printf("%02x ", byte);
for(char bit_idx = 7; bit_idx >= 0; --bit_idx)
{
int bit = (byte >> bit_idx) & 1;
len += snprintf(out + len, sizeof(out) - len - 1, "%d", bit);
}
len += snprintf(out + len, sizeof(out) - len - 1, " ");
}
printf("\nbinary:%s\n", out);
}int main(int argc, char *argv[])
{
float f32 = 11.375f;
print_binary(&f32, sizeof(f32));
return 0;
}
输出:
Hex: 41 36 00 00
binary:01000001 00110110 00000000 00000000和理论计算一致:
例2:理解以下代码打印结果为什么是:f32=100000000.000000,小数点后的数为什么不见了?
int main(int argc, char *argv[])
{
float f32 = 100000000.1f;
printf("f32=%f\n", f32);
}代码执行结果:
f32=100000000.000000
第一步:将(100000000.1)10转换为单精度二进制
整数部分(100000000)10,转换为二进制:
(100000000)10 = (0101111101011110000100000000)2
小数部分:(0.1)10转换二进制
(0.1)10=(0.00011)2
(100000000.1)10=(-1)^0*(1.0111110101111000010000000000011) * 2^26
第二步:从第一步可知,
S=0,M=0111110101111000010000000000011,
e=26, E=e+127=(10011001)2
第三步:内存中理论存储:
0 10011001 01111101011110000100000,由于单精度M只能存储23位,所以 M中的00000011被丢弃,所以最终内存中存储的二进制为:
0 10011001 01111101 01111000 0100000结论:浮点数的整数部分过大,导致小数部分精度丢失
C语言验证:
int main(int argc, char *argv[])
{
float f32 = 100000000.1f;
printf("f32=%f\n", f32);
print_binary(&f32, sizeof(f32));
return 0;
}结果:
f32=100000000.000000
Hex: 4c be bc 20
binary:0 10011001 01111101 01111000 0100000验证结果和理论分析结果一致
例3:将例2中的单精度存储改为双精度存储
double f64 = 100000000.1;
第一步:将(100000000)10转换为双精度的二进制
整数部分(100000000)10,转换为二进制:
(100000000)10 = (0101111101011110000100000000)2
小数部分:(0.1)10转换二进制
(0.1)10=(0.00011001100110011001100110011)2 总的29位,后面还有无穷0011循环
(100000000.1)10=(-1)^0*(1.0111110101111000010000000000011001100110011001100110011) * 2^26,绿色:26位,红色26位,double的M可以存储52bit。
第二步:从第一步可知
S=0,e=26,E=e+1023=(10000011001)2,
M=0111110101111000010000000000011001100110011001100110
第三步:由第一步和第二步可知,浮点数(100000000.1)10在内存中双精度存储的二进制为:
(0 10000011001 01111101 01111000 01000000 00 00011001 10011001 10011001 10)2
C语言验证:
int main(int argc, char *argv[])
{
double f64 = 100000000.1;
printf("f64=%lf\n", f64);
print_binary(&f64, sizeof(f64));
return 0;
}结果:
f64=100000000.100000
Hex: 41 97 d7 84 00 66 66 66
binary:010000011001 01111101 01111000 01000000 00 00011001 10011001 10011001 10验证和理论分析一致:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。