相关文章推荐
爱笑的肉夹馍  ·  Python selenium ...·  1 年前    · 
精明的蚂蚁  ·  c++读取xml配置-掘金·  1 年前    · 
想出国的羽毛球  ·  html - Bootstrap - ...·  1 年前    · 

一种 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);}