浮点数的储存形式
1. 浮点数的二进制表示
举个例子:4.25
在这个浮点数中:4-整数部分,0.25-小数部分
整数部分-直接转换成二进制,即4表示为
(
100
)
2
(100)_2
(100)2;
小数部分-将小数部分乘以2,取小数点前一位作为二进制的高位,对小数点后的部分执行相同的步骤,直到它变成1.0;
0.25 *2=0.50
0.5 *2=1.00
0.25= ( 01 ) 2 (01)_2 (01)2
将整数和小数部分结合到一起:
4.25=
(
100.01
)
2
(100.01)_2
(100.01)2
2. 浮点数的二进制储存
这里说明单精度浮点数(float)的储存格式:
根据 IEEE 754的标准,将32bin的浮点数划分为:
符号位:1位
指数位:8位
有效数字位:24位(23位显式储存)
符号位决定了数据的符号;
指数位是一个从 0 到 255 的 8 位无符号整数。指数值 127 代表实际零,-127代表0;
有效数字包括二进制小数点右侧的 23 个小数位和值为1的隐式前导位。
其具体储存格式如下:
value
=
(
−
1
)
sign
×
2
(
E
−
127
)
×
(
1
+
∑
i
=
1
23
b
23
−
i
2
−
i
)
\text { value }=(-1)^{\text {sign }} \times 2^{(E-127)} \times\left(1+\sum_{i=1}^{23} b_{23-i} 2^{-i}\right)
value =(−1)sign ×2(E−127)×(1+i=1∑23b23−i2−i)
其中:
sign
=
b
31
=
0
(
−
1
)
sign
=
(
−
1
)
0
=
+
1
E
=
b
30
b
29
…
b
23
=
∑
i
=
0
7
b
23
+
i
2
+
i
=
124
2
(
E
−
127
)
=
2
124
−
127
=
2
−
3
1.
b
22
b
21
…
b
0
=
1
+
∑
i
=
1
23
b
23
−
i
2
−
i
=
1
+
1
⋅
2
−
2
=
1.25
\begin{aligned} &\operatorname{sign}=b_{31}=0 \\ &(-1)^{\operatorname{sign}}=(-1)^{0}=+1 \\ &E=b_{30} b_{29} \ldots b_{23}=\sum_{i=0}^{7} b_{23+i} 2^{+i}=124\\ &2^{(E-127)}=2^{124-127}=2^{-3}\\ &1 . b_{22} b_{21} \ldots b_{0}=1+\sum_{i=1}^{23} b_{23-i} 2^{-i}=1+1 \cdot 2^{-2}=1.25 \end{aligned}
sign=b31=0(−1)sign=(−1)0=+1E=b30b29…b23=i=0∑7b23+i2+i=1242(E−127)=2124−127=2−31.b22b21…b0=1+i=1∑23b23−i2−i=1+1⋅2−2=1.25
因此:
value
=
(
+
1
)
×
2
−
3
×
1.25
=
+
0.15625
\text { value }=(+1) \times 2^{-3} \times 1.25=+0.15625
value =(+1)×2−3×1.25=+0.15625
同理双精度浮点数(double)的储存格式:
计算同理单精度。
3. 储存格式的代码说明
下面通过一段代码加深一下对储存格式的理解:
代码主要是通过指针的形式,将4字节的float格式数据的地址及地址中的数据依次打印出来,便于对二进制储存格式的理解
指针变量指向的类型作用:决定了指针变量 所取空间内存的宽度 决定了指针变量+1跳过的单位跨度
#include <stdio.h>
#include <stdlib.h>
int main ()
{
float var_f=0.15625;
int i,num;
char *char_ptr;
num=sizeof(float);
printf("float存储大小:%d \n",num);
char_ptr=(char*)(&var_f);
for ( i = 0; i < num; i++)
{
printf("第%d位的地址:%p\n",i+1,char_ptr+i);
printf("第%d位的数据:%x\n",i+1,*(char_ptr+i));
}
return 0;
}
运行结果为:
通过IEEE-754 Floating Point Converter进行转换有:
可以看到16进制表示时:0x3e200000 与我们的输出结果一致!
STM32的串口传输:
上面我们了解了浮点数的储存格式,那么只要将一个浮点数的4个字节数据分成单个字节进行依次串口传输这样就可以了呀!
发送:
void usart_send_float (float value)
{
float v_float ;
unsigned char * char_p;
unsigned char i,num;
v_float = value;
char_p = (unsigned char *)(&v_float );
num = sizeof(float);
for (i=0;i<num ;i++)
{
USART_SendData(USART1, *(char_p+i));
while( USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
}
接收:
char pc_rx_buf[4];
unsigned char i,num;
float *buff,my_float;
num = sizeof(float);
for (i = 0; i < num; i++)
pc_rx_buf[i] = *(char_ptr + i); //接收来自串口的数据
buff = (float*)(pc_rx_buf);
//从pc_rx_buf[0]开始读取4个字节pc_rx_buf[0]、[1]、[2]、[3]组成一个float
my_float = *buff; //得到数值
//or my_float = *(float*)char_ptr; 进行强制类型转换,将char_ptr转成一个float指针并将地址中保存的数据传给my_float
验证:
#include <stdio.h>
#include <stdlib.h>
int main()
{
float var_f = 0.15625, my_float;
int i, num;
char *char_ptr;
float *buff;
char pc_rx_buf[4];
num = sizeof(float);
printf("float存储大小:%d \n", num);
char_ptr = (char*)(&var_f);
for (i = 0; i < num; i++)
{
printf("第%d位的地址:%p\n", i + 1, char_ptr + i);
printf("第%d位的数据:%x\n", i + 1, *(char_ptr + i));
pc_rx_buf[i] = *(char_ptr + i);
}
buff = (float*)(pc_rx_buf);
printf("pc_rx_buf的地址:%p\n", &pc_rx_buf);
my_float = *buff;
printf("接收的数值为:%f\n", my_float);
return 0;
}
输出结果为:
这样就可实现串口的数据发送与接收,对于整数的传输也是相同道理。
最近实验验证需要,进行了ROS小车的底盘驱动部分的开发,开发任务要进行上位机与下位机的数据交互,其中底盘的角度数据要上传给主控部分,就涉及到了浮点数的串口传输问题,之前调试中,一直是利用串口发送字符串进行数据交互,但在字符串的解算过程中比较麻烦,需要寻找相应的标志位,对于浮点数更是要进行小数点的位置判断,及数据位的长度计算,十分麻烦,参考网上代码中对整数传输的例子,想到了浮点数的传输也同理,故整理记录!