如何打印出浮点数的每一位?

6

我正在尝试在C语言中打印出浮点数的每一位。

对于整数,我可以使用以下代码:

int bit_return(int a, int loc)

// Bit returned at location
{
  int buf = a & 1<<loc;

  if (buf == 0)
    return 0;
  else
    return 1;
}

如果我将int a替换为float a,编译器就无法编译。

这个问题有解决方案吗?


请复制并重新格式化您的评论如下所示:

好的,对于不清楚的人,我在此贴出我的整个代码:

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

int bit_return(int a, int loc) // Bit returned at location
{
  int buf = a & 1<<loc;
  if (buf == 0)
    return 0;
  else
    return 1;
}

int main()
{
  int a = 289642; // Represent 'a' in binary
  int i = 0;
  for (i = 31; i>=0; i--)
  {
    printf("%d",bit_return(a,i));
  }
  return 0;
}

我想用二进制表示2.5。 例如在整数中:1 = 00000001。那么,2.5 = ? - user188276
你能重新发布你的代码吗?其中一部分似乎已经被切断了(没有提到loc)。 - HalfBrian
int bit_return(int a, int loc) // 返回位于位置loc的位 { int buf = a & 1<<loc; if (buf == 0) return 0; else return 1; } - user188276
好的,对于不清楚的人,我在这里发布我的整个代码: #include <stdio.h> #include <stdlib.h>int bit_return(int a, int loc) // 返回位于位置loc的位 { int buf = a & 1<<loc;if (buf == 0) return 0; else return 1;}int main() { int a = 289642;// 以二进制表示a int i = 0; for (i = 31; i>=0; i--) { printf("%d",bit_return(a,i)); } return 0;} - user188276
以下任何答案都没有解决主要问题:他想要的是二进制而不是十六进制表示。 - Matt Joiner
显示剩余6条评论
10个回答

6

感谢Pascal Cuoq的评论。我终于解决了自己的问题。是的,只需要将浮点数的地址赋给整数指针,然后对其进行解引用即可。

以下是我的代码解决方案:

#include <stdio.h>

// bit returned at location
int bit_return(int a, int loc)   
{
    int buf = a & 1<<loc;

    if (buf == 0) return 0;
    else return 1; 
}

int main() 
{
    //11000010111011010100000000000000  
    // 1 sign bit | 8 exponent bit | 23 fraction bits
    float a = -118.625; 
    int *b;
    b = &a;

    int i;
    for (i = 31; i >= 0; i--)
    {
        printf("%d",bit_return(*b,i));
    }

    return 0;
}

为什么要使用 printf("%d",bit_return(*b,i));?使用 printf("%d",bit_return(a,i)); 不是一样的吗?为什么要多一个指针变量? - J...S
@J...S:a 是一个 float*b 旨在将相同的字节重新解释为整数。然而,这违反了 C 的别名规则,因此 C 标准不能保证它能正常工作。 - Eric Postpischil
1
更好的方法是 #include <string.h> … unsigned b; memcpy(&b, &a, sizeof b);,然后将 b 传递给 bit_return 而不是 *b。这遵守了 C 语言检查对象字节的规则。bit_return 应该改为使用 unsigned 而不是 int,并将 1<<loc 更改为 1u<<loc。当 loc 为 31(假设为 32 位 int)时,前者未定义。 - Eric Postpischil
但是这会打印错误。对于a = 2.5,它打印了“00000000000000000000000000000010”。 - Appaji Chintimi

4
static void printme(void *c, size_t n)
{
  unsigned char *t = c;
  if (c == NULL)
    return;
  while (n > 0) {
    --n;
    printf("%02x", t[n]);
  }
  printf("\n");
}

void fpp(float f, double d)
{
  printme(&f, sizeof f);
  printme(&d, sizeof d);
}
  • 有关float参数的说明

    在调用fpp()函数之前,请确保其原型在作用域内,否则您将会遇到一个晦涩难懂的K&R C与ANSI C问题。

更新:二进制输出……

  while (n > 0) {
    int q;
    --n;
    for(q = 0x80; q; q >>= 1)
      printf("%x", !!(t[n] & q));
  }

printf("%02x", t[n]); 只显示字节表示。你能让它显示位吗?我想看到像这样的东西:-118.625 写成 1000010111011010100000000000000 [根据 http://en.wikipedia.org/wiki/IEEE_754-1985] - user188276
@tsubasa -- 你正在学习哪些计算机科学课程? - Heath Hunnicutt

4

将float类型的地址转换为相同大小的int类型的地址,并将该int传递给您现有的函数。


但是不要感到惊讶,如果在编译器的严格别名优化方法中,结果代码仅会打印出一些无关的垃圾信息。 - AnT stands with Russia
2
@AndreyT 是的,我就是无法理解严格别名。我认为标准的意思是 *(int*)(char*)&f 与严格别名兼容,但如果编译器作者不同意,使用联合会更安全。 - Pascal Cuoq
@ Heath 我认为AndreyT所提到的是,gcc可以自豪地假设*(int*)&f在只修改f时没有改变,并进行"优化"。 GCC开发人员喜欢这样搞乱每个人的低级代码,好像有人在2009年选择C是为了速度(而不是使用C来编写低级代码,人们仍然会在2009年这样做)。我在阅读http://lwn.net/Articles/316126/之前就已经形成了自己的想法,但我必须说我基本上同意。够了,别再进行愚蠢的速度基准测试了。C程序员期望从他们的编译器中得到其他东西。 - Pascal Cuoq
严格别名规则基本上规定,类型为T的对象不能通过类型为U*的指针访问,其中TU是不兼容的类型。编译器使用此规则来解决别名问题以更好地优化代码。在这种情况下,它可能会“起作用”,但通常这样的技巧是行不通的。 - AnT stands with Russia
@Pascal - 你链接中描述的行为并不符合C编译器的规范。我知道我们正在谈论gcc。所谓的“严格别名”重排序行为实际上应该被称为“错误的代码生成”。那些没有使用gcc的人永远不会遇到这个问题。 - Heath Hunnicutt
显示剩余5条评论

3
在C语言中,“位(bit)”一词指的是数字的二进制位置表示中的一个元素。在C中,整数使用二进制位置表示,这就是它们拥有“位”的原因。您可以通过按位运算符(逻辑和移位)来“查看”这些位。浮点数不使用该表示法。此外,浮点数的表示法未由语言规范定义。换句话说,在C中,浮点数没有“位”,这也是为什么您无法通过任何合法手段访问其任何“位”的原因,并且为什么您不能对浮点数对象应用任何位运算符。
话虽如此,我猜您可能对表示浮点数的物理位感兴趣。您可以将所占用的内存重新解释为unsigned char元素的数组,并打印每个unsigned char对象的位。这将给出表示对象的所有“物理”位的映射。
但是,这与上面代码中的内容并不完全相同。您上面的代码打印了整数对象的“值表示”的位(即我上面描述的“逻辑”位),而内存重新解释方法将给出对象表示的位(即“物理”位)。但再次说明,在C中,浮点数根据定义没有逻辑位。
后来添加:我感到理解物理位和逻辑位概念之间的差异可能对某些读者来说并不容易。作为另一个可能有助于促进理解的例子,我想指出,并没有任何东西会阻止完全符合C实现在三元硬件上运行,即根本没有物理二进制位的硬件。在这样的实现中,按位操作仍将完美地工作,它们仍将访问二进制位,即每个整数数字的[现在只是想象中的]二进制位置表示的元素。那就是我上面谈到的“逻辑”位。

如果浮点数没有逻辑位,它如何存储在内存中?例如:2.5 - user188276
1
实际上,我非常确定C99规定了IEEE 754浮点数,并且IEEE 754进一步规定了浮点数中每个位的含义。另一方面,C99并没有为整数指定2补码表示(尽管它是普遍存在的)。因此,实际上,浮点数的表示比整数的表示更好地得到了规定。 - Pascal Cuoq
1
浮点数确实有一个位表示。一定数量的位表示指数,其余的表示尾数(通常省略第一个二进制数字以节省一位)。当学习计算机如何处理浮点数据时,这是非常有趣的内容。 - Carl Smotricz
1
C99: http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1336.pdf IEEE754: http://www.validlab.com/754R/nonabelian.com/754/comments/Q754.129.pdf - Pascal Cuoq
1
@DigitalRoss:C语言规范明确定义了整数类型中“值形成位”和“填充位”的概念。原始帖子中的代码仅打印值形成(逻辑)位。使用unsigned char内存重新解释将打印所有位(物理位)。这就是我所说的区别。对于浮点类型,您只能使用后一种方法,因为前者在C中根本不存在。 - AnT stands with Russia
显示剩余10条评论

3
以下代码假设浮点数和指针大小相同,在许多系统上是正确的:
float myfloat = 254940.4394f;
printf("0x%p", *(void**)(&myfloat));

2

从评论中看来,似乎有人想要输出内部表示的位,但这里提供了一个代码,可以按照问题字面上的要求进行操作,而不会像一些人建议的那样丢失转换为整数:

以二进制形式输出浮点数:

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

void output_binary_fp_number(double arg)
{
    double pow2;

    if ( arg < 0 ) { putchar('-'); arg = -arg; }
    if ( arg - arg != 0 ) {
        printf("Inf");
    }
    else {
        /* compare and subtract descending powers of two, printing a binary digit for each */
        /* first figure out where to start */
        for ( pow2 = 1; pow2 * 2 <= arg; pow2 *= 2 ) ;
        while ( arg != 0 || pow2 >= 1 ) {
            if ( pow2 == .5 ) putchar('.');
            if ( arg < pow2 ) putchar('0');
            else {
                putchar('1');
                arg -= pow2;
            }
            pow2 *= .5;
        }
    }

    putchar('\n');

    return;
}

void usage(char *progname) {
    fprintf(stderr, "Usage: %s real-number\n", progname);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv) {
    double arg;
    char *endp;

    if ( argc != 2 ) usage(argv[0]);
    arg = strtod(argv[1], &endp);
    if ( endp == argv[1] || *endp ) usage(argv[0]);

    output_binary_fp_number(arg);

    return EXIT_SUCCESS;
}

请注意,我故意避免使用像frexp/isfinite这样的函数,而是使用简单算术运算,让代码更容易学习。 - ysth

2

我已经包含了一些代码,可以产生十六进制输出,我认为这可能有助于您理解浮点数。以下是一个例子:

double: 00 00 A4 0F 0D 4B 72 42 (1257096936000.000000) (+0x1.24B0D0FA40000 x 2^40)

从下面的代码示例中,您应该很容易就能明白如何输出位。将双精度浮点数的地址转换为unsigned char *,并输出sizeof(double)个字符的位。

由于我想要输出浮点数的指数和有效数字(以及符号位),我的示例代码深入挖掘了IEEE-754标准表示64位“双精度”浮点数在基数2中的位。因此,除了验证编译器和我都同意double表示64位浮点数之外,我不使用sizeof(double)

如果您想要输出任何类型的浮点数的位,请使用sizeof(double)而不是8

void hexdump_ieee754_double_x86(double dbl)
{
    LONGLONG ll = 0;
    char * pch = (char *)&ll;
    int i;
    int exponent = 0;

    assert(8 == sizeof(dbl));

    // Extract the 11-bit exponent, and apply the 'bias' of 0x3FF.
    exponent = (((((char *)&(dbl))[7] & 0x7F) &lt;&lt; 4) + ((((char *)&(dbl))[6] & 0xF0) &gt;&gt; 4) & 0x7FF) - 0x3FF;

    // Copy the 52-bit significand to an integer we will later display
    for (i = 0; i &lt; 6; i ++)
        *pch++ = ((char *)&(dbl))[i];
    *pch++ = ((char *)&(dbl))[6] & 0xF;

    printf("double: %02X %02X %02X %02X %02X %02X %02X %02X (%f)",     
           ((unsigned char *)&(dbl))[0],
           ((unsigned char *)&(dbl))[1],
           ((unsigned char *)&(dbl))[2],
           ((unsigned char *)&(dbl))[3],
           ((unsigned char *)&(dbl))[4],
           ((unsigned char *)&(dbl))[5],
           ((unsigned char *)&(dbl))[6],
           ((unsigned char *)&(dbl))[7],
           dbl);

    printf( "\t(%c0x1.%05X%08X x 2^%d)\n", 
            (((char *)&(dbl))[6] & 0x80) ? '-' : '+',
            (DWORD)((ll & 0xFFFFFFFF00000000LL) &gt;&gt; 32),
            (DWORD)(ll & 0xFFFFFFFFLL),
            exponent);
}

请注意:有效数字以十六进制小数(“0x1.24B0D0FA40000”)显示,指数以十进制(“40”)显示。对我来说,这是一种直观显示浮点位的方式。


1

如果你想在浮点数上使用bit_return函数,你可以简单地作弊:

float f = 42.69;

for .... 
   bit_return((int) f, loc)

(int)转换会让编译器认为你正在使用整数,所以bit_return将起作用。 这本质上就是Pascal建议的内容。
编辑: 我被Pascal纠正了。我认为这将符合他最新的评论:
bit_return (*((float *) &f), loc)

希望这次我理解正确了。
另一个选择(括号更少)是使用联合体来欺骗数据类型。

3
实际上,将 (int) 应用于浮点数会被编译为一个四舍五入的操作。你真的需要取 f 的地址,将其强制转换为指向整数的指针,并解引用它。 - Pascal Cuoq
Pascal Cuoq,非常好的评论。谢谢。我终于弄明白怎么做了。 #include <stdio.h>int bit_return(int a, int loc) // 返回位置处的位 { int buf = a & 1<<loc;if (buf == 0) return 0; else return 1;}int main() { float a = -118.625; //11000010111011010100000000000000 |1 符号位 | 8 指数位 | 23 小数位 | int *b; b = &a;int i; for (i = 31; i>=0; i--) { printf("%d",bit_return(*b,i)); } return 0; } - user188276

0

我认为解决这个问题的最佳方式是使用联合

unsigned f2u(float f)
{
    union floatToUnsiged{
    float a;
    unsigned b;
    }test;

    test.a = f;
    return (test.b);
}

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

0

打印整数部分,然后是'.',再然后是小数部分。

float f = ...
int int_part = floor(f)
int fraction_part = floor((f - int_part) * pow(2.0, 32))

然后,您可以使用bit_return打印x和y。如果不打印前导零和/或尾随零,则会获得额外的奖励分数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接