【问题标题】:Obtaining bit representation of a float in C在 C 中获取浮点数的位表示
【发布时间】:2017-06-17 21:55:17
【问题描述】:

我正在尝试使用联合来获取浮点值的位表示,我的代码目前如下:

union ufloat {
  float f;
  unsigned u;
};

int main( ) {       

   union ufloat u1;
   u1.f = 3.14159f;
   printf("u1.u : %f\n", u1.u);

但是,我尝试打印的任何内容都打印为 0.0000000,而不是位(例如 0001 0110 或类似的东西),我的代码有什么问题?

请注意,我希望使用联合来实现这一点。

【问题讨论】:

  • 没有printf 格式说明符可以打印出任何值的二进制表示。您需要提取每一位并将其打印出来。
  • 您正在调用 未定义的行为 - 将 unsigned 传递给 printf 以对应于 %f
  • 您需要使用十六进制格式。 %X 用于整数,或者%A 用于double(您不能将float 传递给printf();它们会自动转换为double)。
  • @BiteBytes 投射浮动而不是重新解释它
  • @BiteBytes:更糟糕的是:你违反了有效类型(又名严格别名)规则!我不明白你为什么坚持要演员!

标签: c floating-point bit-manipulation


【解决方案1】:

有很多方法可以做到这一点。了解您真正想要做的只是输出内存中构成float 的位。几乎所有 x86 类型的实现都以 IEEE-754 单精度浮点格式存储。在 x86 上是 32 位数据。这就是在将float 转换为unsigned 时允许“窥视”位的原因(两者都是32 位,并且为unsigned 类型定义了位操作)对于x86 以外的实现,甚至在x86 本身,unsigned 的更好选择是uint32_t 提供的精确长度类型stdint.h。这样,大小就不会模棱两可了。

现在,演员表本身在技术上不是问题,它是值的访问,虽然取消了你运行的不同类型(又名 type-punning)违反 strict-aliasing 规则(C11 标准的第 6.5 (7) 节)。 floatuint32_t 类型中的 union 为您提供了一种通过 unsigned 类型窗口查看 float 位的有效方法。 (无论哪种方式,您都在查看相同的位,这只是您访问它们并告诉编译器应该如何解释它们的方式)

也就是说,您可以从此处的所有答案中收集到有用的信息。您可以编写函数来访问和存储float 值的位表示形式以供以后使用,或者将位值输出到屏幕。作为一年左右使用浮点值的练习,我编写了一个小函数来以带注释的方式输出位,从而可以轻松识别符号、标准化指数和尾数。您可以调整它或其他答案例程来满足您的需求。简短的例子是:

#include <stdio.h>
#include <stdint.h>
#include <limits.h> /* for CHAR_BIT */

/** formatted output of ieee-754 representation of float */
void show_ieee754 (float f)
{
    union {
        float f;
        uint32_t u;
    } fu = { .f = f };
    int i = sizeof f * CHAR_BIT;

    printf ("  ");
    while (i--)
        printf ("%d ", (fu.u >> i) & 0x1);

    putchar ('\n');
    printf (" |- - - - - - - - - - - - - - - - - - - - - - "
            "- - - - - - - - - -|\n");
    printf (" |s|      exp      |                  mantissa"
            "                   |\n\n");
}

int main (void) {

    float f = 3.14159f;

    printf ("\nIEEE-754 Single-Precision representation of: %f\n\n", f);
    show_ieee754 (f);

    return 0;
}

使用/输出示例

$ ./bin/floatbits

IEEE-754 Single-Precision representation of: 3.141590

  0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 1 1 1 1 1 1 0 1 0 0 0 0
 |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
 |s|      exp      |                  mantissa                   |

查看一下,如果您有任何问题,请告诉我。

【讨论】:

  • 是的,同意,uint32_t 的确切类型将是工会的更好选择。 (更新)
  • 在破坏代码的独角兽平台上使用的一个有用的预防措施:assert(sizeof fu.f == sizeof fu.u); 我看到的唯一角落是floatuint32_t 的字节序,可能很少会以与预期相反的顺序渲染输出即使binary32
  • 我想我们可以在uint32_t ui = 1U &lt;&lt; 24; if (*(char *)&amp;ui) {..handle big-endian..} 的开头添加一个检查除了assert(sizeof fu.f == sizeof fu.u); 检查。我们会将这些作为评论留给任何可能发现自己骑着这样的野兽的人。
  • 1U &lt;&lt; 24 是那些讨厌的 16 未签名机器上的 UB - 在嵌入式领域很常见。建议其他字节序检测代码 - 可能是 this?
  • 该死的独角兽——你又是对的!这是将sizeof int 用作已定义常量的非常巧妙的用法,但编译器不会反对它。这确实会处理 16 位框(以及任何框,只要 intchar 的偶数倍 - 找到一个不正确的框,你确实有一个罕见的独角兽。 ..) 甚至1U &lt;&lt; (sizeof (int) - 1) * CHAR_BIT
【解决方案2】:

您可以编写一个简单的print_bits-函数并使用一组无符号字符来读出浮点数的“原始内存表示”:

void print_bits(unsigned char x)
{
    int i;
    for (i = 8 * sizeof(x) - 1; i >= 0; i--) {
        (x & (1 << i)) ? putchar('1') : putchar('0');
    }
}

typedef float ftype;

union ufloat {
    ftype f;
    unsigned char bytes[sizeof(ftype)];
};

int main( ) {
    union ufloat u1;
    u1.f = .1234;

    for (int i=0; i<sizeof(ftype); i++) {
        unsigned char b = u1.bytes[i];
        print_bits(b);putchar('-');
    }
    return 0;
}

不确定union 是否真的需要(我想你是因为对齐问题和UB 而引入的);使用unsigned char 的数组时,对齐应该不是问题。

【讨论】:

  • unsigned char x .... i = 8 * sizeof(x) - 1 始终为 i = 7。可以使用i = CHAR_BIT - 1ref.
【解决方案3】:

要将任何变量/对象转换为对二进制进行编码的字符串,请参阅how to print memory bits in c


将 ... 打印为位(例如 0001 0110 或类似的东西),

类似的东西:使用"%a" 打印float,转换为double,以十六进制显示其有效,并以2 的十进制幂显示指数。@Jonathan Leffler

printf("%a\n", 3.14159f);
// sample output
0x1.921fap+1

【讨论】:

  • OP 想要 float 的二进制表示,而不是十六进制格式的浮点数。
  • OP 明确表示“或类似的东西”,因此由于""%a" 是C 标准库的一部分,它需要考虑。通常二进制表示被压缩成十六进制,因为区分一个和另一个是微不足道的。
  • 打印尾数和指数与二进制表示不同。不过,位模式的十六进制输出将是。
【解决方案4】:

二进制输出没有格式说明符;通常为方便起见使用十六进制(以 16 为底),因为一个十六进制数字正好代表 4 个二进制数字。有一个十六进制格式说明符(%x%X)。

printf( "u1.u : %4X\n", u1.u ) ; 

或者,您可以使用itoa()(非标准,但通常实现的功能)生成二进制字符串表示。

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

...

char b[sizeof(float) * CHAR_BIT + 1] = "" ;
printf( "u1.u : %s\n", itoa( u1.u, b, 2 ) ) ; 

问题在于它不包括前导零,并且在二进制浮点表示中所有位都是重要的。可以处理,但有点麻烦:

#define BITS (sizeof(float) * CHAR_BIT + 1) ; 
char b[BITS] = itoa( u1.u, b, 2 ) ;
printf( "u1.u : " ) ;
for( int i = 0; i < BITS - strlen(b); i++ )
{
    putchar( '0' ) ;
} 
printf( "%s\n", b ) ; 

请注意,在上面的示例中,与原始问题中的隐含假设相同,即 unsigned 至少与 float 一样大,并且使用相同的字节顺序(例如,较旧的 ARM 设备使用“cross-endian”浮点格式!)。在这方面,我没有尝试过可移植性。最终,如果您只想检查 float 的内存布局,那么在调试器中进行检查可能是最简单且最独立于编译器实现的方法。

【讨论】:

  • 我认为这个unsigned u = *((unsigned*)&amp;f) ; 是未定义的行为。相反,我读到如果您使用char,则使用联合不是未定义的行为。
  • @Stargateur :严格来说它是未定义的,因为没有要求不同类型的指针可以相互转换,但实际上在大多数平台上,对于大多数类型来说,这不是问题,并且由于这可能是实验性代码,因此可移植性可能不是问题。在任何情况下,您都可以使用联合方法,因为只有您的输出方法有缺陷 - 只需在我使用过 u 的地方使用您的 u1.u。
  • 这假定unsigned,它是一个int,与float具有相同的大小。
  • @BiteBytes :与原始问题中的代码一样 - 可以处理,但可能是一个不同的问题。
  • 类型双关语通过指针调用未定义的行为。只有union 在这里是安全的。如果有兼容的方式,为什么要使用 hack(如果添加了一些断言并且我们忽略了输出是实现定义的)。如果没有 VLA,char b[BITS] 将不起作用(我讨厌它,但 C11 将它们设为可选)。 BITS 不是常数。
【解决方案5】:
#include <stdio.h>

union
{
    float f;
    unsigned int u;
} myun;

int main ( void )
{
    unsigned int ra;

    printf("%p %lu\n",&myun.f,sizeof(myun.f));
    printf("%p %lu\n",&myun.u,sizeof(myun.u));
    myun.f=3.14159F;
    printf("0x%08X\n",myun.u);
    for(ra=0x80000000;ra;ra>>=1)
    {
        if(ra&myun.u) printf("1"); else printf("0");
    }
    printf("\n");

    for(ra=0x80000000;ra;ra>>=1)
    {
        if(ra==0x40000000) printf(" ");
        if(ra==0x00400000) printf(" ");
        if(ra&myun.u) printf("1"); else printf("0");
    }
    printf("\n");

    return(0);
}

0x601044 4
0x601044 4
0x40490FD0
01000000010010010000111111010000
0 10000000 10010010000111111010000

【讨论】:

  • soft-float 不是 ARM-whatever-whatever 的默认值。这取决于 FPU 是否可用。今天很多 ARM MCU 都有一个 FPU(只是它只支持单精度),更大的 ARMv7A 肯定有硬浮点(可能有不同的类型)。并不是说浮点处理的类型对 ARM 有任何影响——所有这些都基于 IEEE754。
  • “这个徘徊在实现定义或未定义的行为......” - 请详细说明。明确允许通过 union 进行类型双关。
  • 我建议在调试器中进行内存检查,而不是编译和反汇编。
  • @Olaf : ARM Cortex-M7 (ARMv7-M) 可以具有双精度 FPU - 因此即使在 ARM MCU 上,现在也可以广泛使用完整的双精度硬件支持。
  • @Olaf,只要阅读规范,任何版本。不管多少年、几十年,等待有人显示实际显示它的文本是受支持的。我仍然找不到版本。欢迎您发布答案并附上文本。我将删除评论。我提供了一个联合解决方案,它显示二进制文件,甚至可以隔离位域。 OP要求什么。在我发布这个的时候,这是唯一一个这样做的。加上一些其他的方法,但是downvote,有趣,但也意料之中,这是宗教和政治话题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多