计算机中浮点数的表示
浮点数转二进制浮点数
整数部分:
- 整数部分不断除以2,保存商和余数。
- 判断商是否等于0,如果等于0到第3步,否则回到第1步。
- 将每一步得到的余数倒序保存,得到整数部分的二进制表示。
小数部分:
- 小数部分不断乘以2,保存结果的整数和小数。
- 判断结果的整数是否等于1,如果等于1到第3步,否则回到第1步。
- 将每一步结果的所有整数顺序保存,就得到小数的二进制表示
例如:浮点数 4.8125
整数部分:
4/2 = 2 0
2/2 = 1 0
1/2 = 0 1
所以整数部分 4 的二进制为 100
小数部分:
0.8125*2 = 1.625 1
0.625*2 = 1.25 1
0.25*2 = 0.5 0
0.5*2 = 1.0 1
所以小数部分 0.8125 的二进制位 1101
将整数与小数部分连接起来就是 100.1101
,即=
再如:浮点数 0.05
因为只有小数所以只处理小数部分:
0.05*2 = 0.1 0
0.1*2 = 0.2 0
0.2*2 = 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
0.4*2 = 0.8 0
0.8*2 = 1.6 1
0.6*2 = 1.2 1
0.2*2 = 0.4 0
0.4*2 = 0.8 0
0.8*2 = 1.6 1
...... 无限循环
所以0.05的二进制 = 0.0000 1100 1100 1100 1100 1100 1101 ....
可以看到二进制小数是无法准确表示浮点数的,所以就有了精度一说.
单精度浮点数用32位二进制表示如下:
符号位1位 阶码8位 小数位23位
单精度.jpg
双精度浮点数用64位二进制表示如下:
符号位1位 阶码11位 小数位52位
双精度.png
由上可知,
单精度浮点数精度为 pow(2,23) = 8388608 = 0.8388608 x pow(10,7)
所以单精度浮点数对应的10进制精度为 7
位多
双精度浮点数精度为 pow(2,52)-1 = 4503599627370496 = 0.4503599627370496 x pow(10,16)
所以双精度浮点数对应的10进制精度为 16
位多
浮点数的计算机表示 (IEEE754标准)
移码(又叫增码)是符号位取反的补码,一般用指数的移码减去1来做浮点数的阶码,
引入的目的是便于浮点数运算时的对阶操作。为了保证浮点数的机器零为全0。
对于定点整数,计算机一般采用补码的来存储。
正整数的符号位为 0,反码、补码、原码都一样。
负整数的符号位为 1,原码、反码和补码的表示各不相同,
由原码变成反码和补码有如下规则:
- 原码符号位为1不变,整数的每一位二进制数位取反得到反码;
- 反码符号位为1不变,反码数值位最低位加1得补码。
比如,以一个字节 8bits 来表示 -3,
那么-3的各种码,原码-->反码-->补码--->移码
原码:[ − 3 ] = 1 000001 1,最高位1位符号位
反码:[ − 3 ] = 1 111110 0,原码符号位不变,其他位取反
补码:[ − 3 ] = 1 111110 1,反码符号为不变,最低位加1
移码:[ − 3 ] = 0 111110 1,补码符号为取反
浮点数二进制表示:
s:符号位,0正数,1负数
M:小数部分,使用原码表示
E:阶码,使用其原码+127表示或者用其移码-1表示,
比如十进制4.5的单精度浮点数的二进制 = 表示为上述公式则为
看到这里的E
为2
,那么它在计算机实际存储为 2 + 127 = 129
=
根据上面的公式各部分表示的规则得到如下表:
符号位s:1位 | 阶码E:2~9位 | 小数位M:10~32位 |
---|---|---|
0 | 2+127=129 | 001 |
0 | 10000001 | 00100000000000000000000 |
符号位为0
表示是正数,所以4.5
的二进制存储为0 10000001 00100000000000000000000
,即 = 0x40900000
以下tool.c
是一个测试工具:
// tool.c
#include <stdio.h>
#include <stdlib.h>
// 打印一个计算机存储的浮点数值
void print_bin_to_float(unsigned long hex){
float* fp=(float*)&hex;
printf("%lx,%f\n",hex,*fp);
}
// 打印一个浮点数在计算机存储的十六进制表示
void print_float_to_bin(float f){
unsigned long* pf = (unsigned long*)&f;;
printf("%f,0x%lx\n",f,*pf);
}
// 反转字符串
char * reverse_str(char *str, long len){
char *start = str;
char *end = str + len - 1;
char ch;
if (str != NULL)
{
while (start < end)
{
ch = *start;
*start++ = *end;
*end-- = ch;
}
}
return str;
}
// 十六进制转二进制字符串
char * hex_to_bin(unsigned long long hex,int bin_max_len){
printf("0x%llx,hex_to_bin:\n",hex);
char bin_ar[1024] = {0};
long count = 0;
unsigned long long quotient = 0;
int remainder = 0;
do{
quotient = hex / 2;
remainder = hex % 2;
//转换成显示的数字字符
char save = '0' + remainder;
bin_ar[count] = save;
hex = quotient;
count ++;
} while (quotient != 0);
while (count < bin_max_len){
bin_ar[count] = '0';
count ++;
}
char *result = reverse_str(bin_ar,count);
printf("0b%s\n",result);
printf("\n");
return result;
}
int main(){
print_float_to_bin(4.5); // 0x40900000
print_float_to_bin(0.5); // 0x3f000000
print_float_to_bin(0.05); // 0x3d4ccccd
print_float_to_bin(0.15625); // 0x3e200000
/*
4.5
0x40900000,hex_to_bin:
0b01000000100100000000000000000000
*/
hex_to_bin(0x40900000,32);
return 0;
}
下面是我们把上述二进制转为二进制浮点数
4.5
0x40900000 十六进制表示
sign exponent fraction
0 10000001 00100000000000000000000
0 129 00100000000000000000000
阶码 129-127 = 2
0 2 00100000000000000000000
先向右移动小数点2位
0 2 00.100000000000000000000
在向首位补1
0 2 1 00.100000000000000000000
结果 100.100000000000000000000
如果得到阶码是负数比如下面的0.5
和0.05
,规则是先在首位补1
,然后向左移动小数点,不够补0
0.5
0x3f000000 十六进制表示
sign exponent fraction
0 01111110 00000000000000000000000
0 01111110 00000000000000000000000
0 126 00000000000000000000000
阶码 126-127 = -1
0 -1 00000000000000000000000
先在左侧补1
0 -1 1 00000000000000000000000
再向左移动小点1位
0 -1 0.1 00000000000000000000000
结果就是 0.1 00000000000000000000000
0.05
0x3d4ccccd 十六进制表示
sign exponent fraction
0 01111010 10011001100110011001101
0 122 10011001100110011001101
阶码 122-127 = -5
0 -5 10011001100110011001101
先在左侧补1
0 -5 1 10011001100110011001101
再向左移动小点5位,不够插入0
0 -1 0.00001 10011001100110011001101
结果就是 0.0000110011001100110011001101
综上我们知道
阶码为正小数点右移
,先移动小数点再补1
。
阶码为负小数点左移
,先补1
再移动小数点,不足补0
二进制浮点数转10进制浮点数,这里是单精度,双精度同理
0.15625
0x3e200000
sign exponent fraction
0 01111100 01000000000000000000000
0 01111100 01000000000000000000000
0 124 01000000000000000000000
0 -3 01000000000000000000000
先在左侧补1
0 -3 1 01000000000000000000000
再向左移动小点3位,不够插入0
0 -3 0.001 01000000000000000000000
结果就是 0.00101000000000000000000000 = 0.00101
根据公式定义:
0.15625
二进制表示如下:
转换如下: