一种 float 和 int 相互转换的方法
在编程过程中,我们有时候会遇到 float 和 int 相互转换的情况,这里分享一种转换方法。
首先说明一下,本文中使用 C++ 语言做演示,float 为 32 位 IEEE 754 标准的浮点数 ,int 为 32 位整型。
本文 float 转 int 的目标是直接丢弃小数部分,而不是四舍五入。
1、float 与 int 互相转换(正数)
举个例子,假设在 C 语言中有 如下的 f = 11.75, 我们现在尝试将 11.75 转换成int。
float f = 11.75f;
十进制的 11.75,我们先看看其对应的 IEEE 标准的浮点数对应的二进制表示,首先 (11.75)_{10} = (1011.11)_{2} (+1)\times (1.01111)_{2} \times (2^{(130 -127)}) ,其32位二进制码(从左到右为 _{31 ......0} 位)如下:
0 1 0 0 0 0 0 1 | 0 0 1 1 1 1 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 |
在这里首先给出参考文献中给出的方法的代码,然后我们再逐步分析
union IntFloat
int i;
float f;
int FloatToInt(float source)
IntFloat ret, bias;
ret.f = source;
bias.i = ((23 + 127) << 23); // 该数对应的浮点数为 1.0 * 2^23
ret.f += bias.f;
ret.i -= bias.i;
return ret.i;
在这里我们使用了一个 “magic number”bias =
1.0\times2^{23}
, 第一步是将我们原先的浮点数加上这个 bias, 我们可以看一下这个加法的二进制版本,这样会更清晰一点。
(0100, 0001, 0011, 1100, 0000, 0000, 0000, 0000)-------> 11.75 +
(0100, 1011, 0000, 0000, 0000, 0000, 0000, 0000)-------> 2^{23} =
(0100, 1011, 0 000, 0000, 0000, 0000, 0000, 1011) -------> ans
对于浮点数加法,首先是调整指数,对于原数据 (1011.11)_{2} 要与 2^{23} 相加,首先需要将其指数调整到 23,其尾数就需要右移 (23 - 3) 位,此时尾数的数据是 ans 中加粗的结果;这时若使用整型减法, 用 ans 减去 2^{23}
那么我们就能得到结果:
(0000, 0000, 0
000, 0000, 0000, 0000, 0000, 1011)
把这个二进制串当成整型,即为我们所需要的结果 11。
int 转成 float的过程,即为上面过程的逆过程。
需要注意的是,上面方法只对 f < 2^23 时可用,因为我们指数对齐是向2^23 对其的。
2、float 与 int 互相转换(负数)
对于负数的转换,我们需要使用 bias = 1.5\times 2^{23} ,也就是下面的代码,然后执行上面的转换就行了。
bias.i = ((23 + 127) << 23) + (1 << 22); // 该数对应的浮点数为 1.5 * 2^23
这里举例说明一下,我们令 f = -11.75 = (-1)\times (1.01111)_{2} \times (2^{(130 -127)}) ,其二进制码如下
1 1 0 0 0 0 0 1 | 0 0 1 1 1 1 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 |
我们先来思考一下,假设我们已经得到了 11 的二进制补码,如何找到 -11 的补码。假设我们有一个 5 位补码系统,那么其表示范围为 (-16 ~ 15),我们要找 -11的补码,只需要使用 (1 00000)_{2} - 11 得到的结果的 后5位就是其补码。
带着上面的例子,我们来看为什么是 bias = 1.5\times 2^{23} ,首先将 -11.75 + 1.5\times 2^{23} ,过程如下
(0100, 0001, 0011, 1100, 0000, 0000, 0000, 0000)-------> 11.75 +
(0100, 1011, 0100, 0000, 0000, 0000, 0000, 0000)-------> 1.5 * 2^{23}
第一步 ==>> (指数对其),尾数右移
(1100, 1011, 0 <---> 000, 0000, 0000, 0000, 0000, 1011 )-------> (0.0\cdot\cdot\cdot0 1011)_{2} \times 2^{23}
(0100, 1011, 0 <---> 100, 0000, 0000, 0000, 0000, 0000 )-------> 1.5 * 2^{23}
第二步 ==> 尾数相减(也就是上面加粗的部分,下面减上面)
得到结果
(0100, 1011, 0 011, 1111, 1111, 1111, 1111, 0101 ) ---> ans
(0100, 1011, 0100, 0000, 0000, 0000, 0000, 0000)-------> 1.5 * 2^{23}
我们可以看到,ans 的尾数的 第 0~21 位二进制串即为 -11 的补码的二进制串的第 0 ~ 22 位(这个得到补码的方法,就如同我们上面斜体文字中的例子一样),这时候我们能把ans 的第 22 ~ 31 位全部置为1,那么得到的结果就是 -11 的整型补码结果了。再观察一下,此时若我们将 ans - 1.5 \times 2^{23} ,那么得到的结果就是将 ans 的第 22 ~ 31 位全部置为1的结果。
注意:由于我们使用了一位尾数来获得负数的补码,所以在负数部分只能获取到 22 位精度,比正数少一位,也就是当 f < 2^22 时候,该转换才有效果。
下面放上总的代码
union FloatInt
std::int32_t i;
float f;
std::int32_t FloatToInt(float f)
FloatInt ret, bias;
ret.f = f;
bias.i = (23 + 127) << 23;
if(f < 0.0f){bias.i = ((23 + 127) << 23) + (1 << 22);}
ret.f += bias.f;
ret.i -= bias.i;
return ret.i;
float IntToFloat(std::int32_t i)
FloatInt ret, bias;
ret.i = i;
bias.i = (23 + 127) << 23;
if(i < 0){bias.i = ((23 + 127) << 23) + (1 << 22);}