C 位级 Int 到 Float 转换意外输出

C Bit-Level Int to Float Conversion Unexpected Output

背景:
我正在玩位级编码(这不是家庭作业 - 只是好奇)。我在网上和一本名为 Hacker's Delight 的书中找到了很多好的 material,但是我遇到了一个在线问题。

它要求将整数转换为浮点数。我使用以下链接作为解决问题的参考:

How to manually (bitwise) perform (float)x?
How to convert an unsigned int to a float?
http://locklessinc.com/articles/i2f/

问题与疑问:
我以为我已经足够了解这个过程(我试图在评论中记录这个过程),但是当我测试它时,我不明白输出。

测试用例:
float_i2f(2) returns 1073741824
float_i2f(3) returns 1077936128

我希望看到类似 2.0000 和 3.0000 的结果。

我是不是把转换搞砸了?我想这可能是一个内存地址,所以我想也许我在访问实际数字所需的转换步骤中遗漏了一些东西?或者也许我打印不正确?我正在这样打印我的输出:

printf("Float_i2f ( %d ): ", 3);
printf("%u", float_i2f(3));
printf("\n");

但我认为打印方法对于 C 中的无符号值很好(我习惯在 Java 中编程)。

感谢任何建议。

代码:

/*
    * float_i2f - Return bit-level equivalent of expression (float) x
    *   Result is returned as unsigned int, but
    *   it is to be interpreted as the bit-level representation of a
    *   single-precision floating point values.
    *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
    *   Max ops: 30
    *   Rating: 4
    */
    unsigned float_i2f(int x) {
        if (x == 0){
            return 0;
        }

        //save the sign bit for later and get the asolute value of x
        //the absolute value is needed to shift bits to put them
        //into the appropriate position for the float
        unsigned int signBit = 0;
        unsigned int absVal = (unsigned int)x;

        if (x < 0){
            signBit = 0x80000000;
            absVal = (unsigned int)-x;
        }

        //Calculate the exponent
        // Shift the input left until the high order bit is set to form the mantissa.
        // Form the floating exponent by subtracting the number of shifts from 158.
        unsigned int exponent = 158; //158 possibly because of place in byte range

        while ((absVal & 0x80000000) == 0){//this checks for 0 or 1. when it reaches 1, the loop breaks
            exponent--;
            absVal <<= 1;
        }

        //find the mantissa (bit shift to the right)
        unsigned int mantissa = absVal >> 8;

        //place the exponent bits in the right place
        exponent = exponent << 23;

        //get the mantissa
        mantissa = mantissa & 0x7fffff;

        //return the reconstructed float
        return signBit | exponent | mantissa;
    }

从评论继续。您的代码是正确的,您只是在查看由 IEEE-754 单精度浮点数中的位组成的 等价物 unsigned integer。 IEEE-754 单精度数字格式(由符号、扩展指数和尾数组成)可以解释为 float,或者那些相同的位可以解释为 unsigned integer(仅由 32 位组成的数字)。您正在输出浮点数的 无符号等价物

你可以用一个简单的并集来确认。例如:

#include <stdio.h>
#include <stdint.h>

typedef union {
    uint32_t u;
    float f;
} u2f;

int main (void) {

    u2f tmp = { .f = 2.0 };
    printf ("\n u : %u\n f : %f\n", tmp.u, tmp.f);

    return 0;
}

示例Usage/Output

$ ./bin/unionuf

 u : 1073741824
 f : 2.000000

如果您还有其他问题,请告诉我。很高兴看到您的研究得出了正确的浮点转换结果。 (另请注意关于 truncation/rounding 的第二条评论)

我只是在这里插话,因为没有解决任何关于字节序的具体问题。那我们就来说说吧。

  1. 原始问题中值的构造与字节顺序无关,使用移位和其他按位运算。这意味着无论您的系统是大端还是小端,实际值都是相同的。区别在于它在内存中的字节顺序。

  2. IEEE-754 普遍接受的约定是字节顺序是大端字节序(尽管我相信没有正式的规范,因此没有要求实现遵循它)。这意味着如果您想直接将整数值解释为浮点数,则需要按大端字节顺序排列。

因此,如果且仅当您知道系统上浮点数和整数的字节顺序相同时,您可以将此方法与联合结合使用.

在常见的基于 Intel 的架构上,这是不行的。在这些架构上,整数是小端,浮点数是大端。您需要将您的价值转换为大端。一个简单的方法是重新打包它的字节 即使它们已经是大端:

uint32_t n = float_i2f( input_val );
uint8_t char bytes[4] = {
    (uint8_t)((n >> 24) & 0xff),
    (uint8_t)((n >> 16) & 0xff),
    (uint8_t)((n >> 8) & 0xff),
    (uint8_t)(n & 0xff)
};
float fval;
memcpy( &fval, bytes, sizeof(float) );

我要强调的是,如果您尝试将整数表示重新解释为 float 或相反,您只需要担心这一点。

如果您只是想输出以位为单位的表示形式,则无需担心。您可以只以有用的形式显示整数,例如十六进制:

printf( "0x%08x\n", n );