使用按位运算将 Int 转换为 Float 或将 Float 转换为 Int(软件浮点)

Posted

技术标签:

【中文标题】使用按位运算将 Int 转换为 Float 或将 Float 转换为 Int(软件浮点)【英文标题】:Converting Int to Float or Float to Int using Bitwise operations (software floating point) 【发布时间】:2013-12-16 15:53:38 【问题描述】:

我想知道您是否可以帮助解释将整数转换为浮点数或将浮点数转换为整数的过程。对于我的课程,我们将只使用位运算符来做到这一点,但我认为对从类型到类型的转换的深刻理解将在这个阶段帮助我更多。

据我目前所知,要使 int 变为浮点数,您必须将整数转换为二进制,通过找到有效数、指数和小数来标准化整数的值,然后从那里输出浮点值?

至于float转int,你得把值分成有效数、指数和小数,然后把上面的指令倒过来得到一个int值?


我尝试按照以下问题的说明进行操作:Casting float to int (bitwise) in C。 但我真的无法理解它。

另外,有人可以解释为什么在将 int 转换为 float 时需要对大于 23 位的值进行舍入吗?

【问题讨论】:

Casting float to int (bitwise) in C 是 type-punningbinary32 位模式转换为 unsigned int 转换(带舍入)到最接近的整数。类型双关将是实现软件浮点的第一步,它将尾数移动指数以将小数点对齐在正确的位置。 (其实是小数点,因为这是以2为底,不是以10为底,所以“十进制”这个词是错误的。) 重复:How to manually (bitwise) perform (float)x?、How to convert an unsigned int to a float?、Casting float to int (bitwise) in C 【参考方案1】:

首先,如果您想更好地理解浮点的弱点,您应该考虑阅读一篇论文:“What Every Computer Scientist Should Know About Floating Point Arithmetic”http://www.validlab.com/goldberg/paper.pdf

现在来点肉吧。

以下代码是简单的代码,它试图从 0 24 范围内的 unsigned int 生成 IEEE-754 单精度浮点数。这是您在现代硬件上最有可能遇到的格式,也是您在原始问题中似乎引用的格式。

IEEE-754 单精度浮点数分为三个字段:单个符号位、8 位指数和 23 位有效位(有时称为尾数)。 IEEE-754 使用 隐藏的 1 有效位,这意味着有效位实际上是总共 24 位。这些位从左到右打包,符号位在 31 位,指数在 30 .. 23 位,有效位在 22 .. 0 位。***的下图说明:

指数的偏差为 127,这意味着与浮点数关联的实际指数比指数字段中存储的值小 127。因此,0 的指数将被编码为 127。

(注意:您可能会对完整的***文章感兴趣。参考:http://en.wikipedia.org/wiki/Single_precision_floating-point_format)

因此,IEEE-754 编号 0x40000000 解释如下:

位 31 = 0:正值 位 30 .. 23 = 0x80:指数 = 128 - 127 = 1(又名 21) 位 22 .. 0 均为 0:有效数 = 1.00000000_00000000_0000000。 (注意我恢复了隐藏的1)。

所以值是 1.0 x 21 = 2.0。

要将上面给出的有限范围内的unsigned int 转换为 IEEE-754 格式的内容,您可以使用如下所示的函数。它采取以下步骤:

将整数的前导 1 与浮点表示中隐藏 1 的位置对齐。 在对齐整数时,记录所做的移位总数。 掩盖隐藏的 1。 使用进行的移位次数,计算指数并将其附加到数字。 使用reinterpret_cast,将生成的位模式转换为float。这部分是一个丑陋的 hack,因为它使用了类型双关指针。您也可以通过滥用union 来做到这一点。一些平台提供了一个内在的操作(例如_itof)来使这种重新解释不那么难看。

有很多更快的方法可以做到这一点;如果不是超级有效的话,这个是为了教学有用:

float uint_to_float(unsigned int significand)

    // Only support 0 < significand < 1 << 24.
    if (significand == 0 || significand >= 1 << 24)
        return -1.0;  // or abort(); or whatever you'd like here.

    int shifts = 0;

    //  Align the leading 1 of the significand to the hidden-1 
    //  position.  Count the number of shifts required.
    while ((significand & (1 << 23)) == 0)
    
        significand <<= 1;
        shifts++;
    

    //  The number 1.0 has an exponent of 0, and would need to be
    //  shifted left 23 times.  The number 2.0, however, has an
    //  exponent of 1 and needs to be shifted left only 22 times.
    //  Therefore, the exponent should be (23 - shifts).  IEEE-754
    //  format requires a bias of 127, though, so the exponent field
    //  is given by the following expression:
    unsigned int exponent = 127 + 23 - shifts;

    //  Now merge significand and exponent.  Be sure to strip away
    //  the hidden 1 in the significand.
    unsigned int merged = (exponent << 23) | (significand & 0x7FFFFF);


    //  Reinterpret as a float and return.  This is an evil hack.
    return *reinterpret_cast< float* >( &merged );

您可以使用检测数字中前导 1 的函数来提高此过程的效率。 (有时这些名称的名称为 clz 表示“计数前导零”,或 norm 表示“规范化”。)

您还可以通过记录符号、获取整数的绝对值、执行上述步骤,然后将符号放入数字的第 31 位,将其扩展到有符号数。

对于 >= 224 的整数,整个整数不适合 32 位浮点格式的有效位字段。这就是您需要“舍入”的原因:您丢失 LSB 以使值适合。因此,多个整数最终将映射到相同的浮点模式。确切的映射取决于舍入模式(向 -Inf 舍入,向 +Inf 舍入,向零舍入,向最接近的偶数舍入)。但事实是,你不能将 24 位推入少于 24 位而不会有损失。

您可以根据上面的代码看到这一点。它通过将前导 1 与隐藏 1 位置对齐来工作。如果值 >= 224,则代码需要右移,而不是,这必然会移开 LSB。舍入模式只是告诉您如何处理移位的位。

【讨论】:

希望那里有足够的信息可以帮助您扭转这一过程。 :-) 肯定有 :) 尤其是在我们关于另一个问题的聊天中。你帮了我很多,再次感谢乔 :) 嗨,乔,我还有一个问题要问你。有利的一面是,我相信所有有效数字都与 0x7FFFFF 进行位与运算的程度!因此,非常感谢您到目前为止的帮助:) 但是,当我尝试使用 0x7FFFFF “立即 0x007FFFFF 不能用 0-255 向左移动 0-23 或全部重复时,我收到了这条消息,奇数字节或偶数字节”那么你认为我可以用另一种方式去掉第 23 位吗? 啊,这是指令中常量的 ARM 汇编限制。您必须从常量池中对其进行 LDR,或者使用不同的指令。您真正需要的是清除第 23 位,因此 BIC 可能是一个合理的选择。 (即BIC ..., #0x00800000)。我最近没有编写太多 ARM 程序集,但我认为这是有效的。 在 ARM 中加载十六进制值时,我总是有点困惑!但我使用了:“ldr r6, =0x7FFFFF”、“AND r0, r6”,其中 r0 是有效数。我相信这会奏效……或者至少我希望如此。而且我也相信您对 bitclear 也是正确的。当我单步执行程序时,我的指令:"ldr r1, =1", "lsl r1, 23" 也变成了 0x00800000 :)【参考方案2】:

您检查过 IEEE 754 浮点表示吗?

在 32 位标准化形式中,除了“0”之外,它具有(尾数)符号位、8 位指数(我认为是超过 127)和 23 位“十进制”尾数。被丢弃(总是以这种形式)并且基数是 2,而不是 10。即:MSB 值为 1/2,下一位为 1/4,依此类推。

【讨论】:

【参考方案3】:

Joe Z 的答案很优雅,但输入值的范围非常有限。 32 位浮点数可以存储以下范围内的所有整数值:

[-224...+224] = [-16777216...+16777216]

以及此范围之外的一些其他值。

整个范围都会被这个覆盖:

float int2float(int value)

    // handles all values from [-2^24...2^24]
    // outside this range only some integers may be represented exactly
    // this method will use truncation 'rounding mode' during conversion

    // we can safely reinterpret it as 0.0
    if (value == 0) return 0.0;

    if (value == (1U<<31)) // ie -2^31
    
        // -(-2^31) = -2^31 so we'll not be able to handle it below - use const
        // value = 0xCF000000;
        return (float)INT_MIN;  // *((float*)&value); is undefined behaviour
    

    int sign = 0;

    // handle negative values
    if (value < 0)
    
        sign = 1U << 31;
        value = -value;
    

    // although right shift of signed is undefined - all compilers (that I know) do
    // arithmetic shift (copies sign into MSB) is what I prefer here
    // hence using unsigned abs_value_copy for shift
    unsigned int abs_value_copy = value;

    // find leading one
    int bit_num = 31;
    int shift_count = 0;

    for(; bit_num > 0; bit_num--)
    
        if (abs_value_copy & (1U<<bit_num))
        
            if (bit_num >= 23)
            
                // need to shift right
                shift_count = bit_num - 23;
                abs_value_copy >>= shift_count;
            
            else
            
                // need to shift left
                shift_count = 23 - bit_num;
                abs_value_copy <<= shift_count;
            
            break;
        
    

    // exponent is biased by 127
    int exp = bit_num + 127;

    // clear leading 1 (bit #23) (it will implicitly be there but not stored)
    int coeff = abs_value_copy & ~(1<<23);

    // move exp to the right place
    exp <<= 23;

    union
    
        int rint;
        float rfloat;
    ret =  sign | exp | coeff ;

    return ret.rfloat;

当然还有其他方法可以找到 int 的 abs 值(无分支)。类似地计算前导零也可以在没有分支的情况下完成,因此将此示例视为示例;-)。

【讨论】:

return *((float*)&amp;ret); 在 C 中是未定义的行为(一些编译器,如 MSVC 确实定义了行为,但其他编译器可能会失败)。使用memcpy 或联合来输入双关语。 我希望你能修正你的答案,并让未来的读者知道。顺便说一句,使用unsigned int(或者在这种情况下更好uint32_t)来移位/或FP位模式也是一个好主意。我认为,改变符号位的有符号整数的左移在技术上是有符号溢出 UB。 (您实际上并没有这样做,并且还有足够多的其他假设,即 2 的补码和 32 位 int,这可能并不重要。) 另外,有符号值的右移是实现定义的,而不是未定义的。 @PeterCordes 您确实意识到return (float)INT_MIN; 毫无意义,因为这是将int 转换为float?另外,如果值为unsigned,也可以在代码中处理。 这不是毫无意义的,它返回正确的 FP 值,就像 0.0 使用 FP 常量返回全零位模式一样。两者都要求编译器知道如何在编译时生成 FP 位模式。如果它被写成-2147483648.0f,而不是包括可能的运行时 int->float 转换并具有循环依赖,也许你会更高兴?注释仍然显示实际的位模式。

以上是关于使用按位运算将 Int 转换为 Float 或将 Float 转换为 Int(软件浮点)的主要内容,如果未能解决你的问题,请参考以下文章

请问C语言中如何将int转换为float

位运算符的问题,&不能进行int,float运算。为啥?

ActionScript 3 AS3使用按位运算符将十六进制值转换为RGB

按位浮点到 Int

使用按位运算符查找每个偶数位是不是设置为 0

Java - 将 Int 转换为字节 - 奇怪的结果