打印 float/double 的二进制表示对于两个不同的数字给出相同的结果

Printing a binary representation of float/double gives the same result for two different numbers

我必须(从内存中)打印浮点数/双精度数的二进制表示形式。虽然它对整数工作得很好,但我对一些浮点数有奇怪的行为。

我的意思是,3.14 和 21.37 的结果相同(双倍)。对于 8.5,我得到 0。

21.37 双倍:

0101000111101011100001010001111101010001111010111000010100011111

3.14 为双:

0101000111101011100001010001111101010001111010111000010100011111

8.5 为双倍:

0000000000000000000000000000000000000000000000000000000000000000

#include <stdio.h>
int printBit(int c, int i) {
    return (c & (1 << (i - 1))) ? 1 : 0;
}

int main()
{
    double f;
    int *b;
    scanf("%lf", &f);

    b = &f;

    int i;
    printf("Jako double: \n");
    for (i = sizeof(f) * 8; i > 0; i--)
    {
        printf("%i", printBit(*b, i));
    }

    printf("\n");
}

"pragmatic" 的答案是 sizeof(int)sizeof(double) 在您的平台上可能不一样;后者可能更大,所以你只读回 double 的一部分,这在两种情况下都是相同的。

但为了更深入地研究,语句 b = &f; 违反了 严格别名 ,因此您的程序的行为在 b 点未定义被取消引用。

一个很好的方法是通过将 &f 强制转换为 const unsigned char* 来读取内存(这是严格别名规则的一个例外),并对该指针使用指针算法直到 sizeof(f).

你的功能肯定没有打印正确的数据。

问题是您系统中的 sizeof(int) 小于 sizeof(double)。也存在严格的别名问题。这实际上是严格理论上的,但一般来说根本不要使用指针双关语。

那么如何双关类型:

  1. 使用联合
  2. 使用 memcpy
  3. 使用字符数组。

方法一和方法三如下所示。

联盟双关 (https://godbolt.org/z/qxi8sK)

void prinFloatAsUnsigned(float x)
{
    union
    {
        uint32_t u;
        float f;
    }u32 = {.f = x};

    printf("sizeof(float) = %zu flaot as unsigned = 0x%x\n", sizeof(float), u32.u);
}

int main(void)
{
    prinFloatAsUnsigned(3.14f);
}

memcpy 方法

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

void printasbin(void *buf)
{
    uint64_t val;

    memcpy(&val, buf, sizeof(val));
    for(int x = 63; x >=0; x--)
        printf("%c", '0' + !!(val & (1ULL << x)));
    printf("\n");
}


int main(void)
{
    double x = 3.14;
    printf("%f = \t",x);
    printasbin(&x);
    x = 21.37;
    printf("%f = \t",x);
    printasbin(&x);
    x = 8.5;
    printf("%f = \t",x);
    printasbin(&x);
}

结果:

3.140000  =     0100000000001001000111101011100001010001111010111000010100011111
21.370000 =     0100000000110101010111101011100001010001111010111000010100011111
8.500000  =     0100000000100001000000000000000000000000000000000000000000000000

https://godbolt.org/z/3mLHw9

或按您的方式打印(稍作修改 - 在 IT 中,我们从零开始计算位)

int printBit(void *buff, int i) 
{
    unsigned char *data = buff;
    return !!(data[i / 8] & (1 << (i & 7)));
}


int main(void)
{
    double f = 3.14;
    printf("Jako double: %f\n", f);
    for (int i = sizeof(f) * 8 - 1; i >= 0; i--)
    {
        printf("%d", printBit(&f, i));
    }
    printf("\n");

    f = 21.37;
    printf("Jako double: %f\n", f);
    for (int i = sizeof(f) * 8 - 1; i >= 0; i--)
    {
        printf("%d", printBit(&f, i));
    }
    printf("\n");

    f = 8.5;
    printf("Jako double: %f\n", f);
    for (int i = sizeof(f) * 8 - 1; i >= 0; i--)
    {
        printf("%d", printBit(&f, i));
    }
    printf("\n");

结果:

Jako double: 3.140000
0100000000001001000111101011100001010001111010111000010100011111
Jako double: 21.370000
0100000000110101010111101011100001010001111010111000010100011111
Jako double: 8.500000
0100000000100001000000000000000000000000000000000000000000000000

https://godbolt.org/z/Gn4vpK