【问题标题】:What is wrong with this int to float conversion in C?这个 int 到 C 中的浮点转换有什么问题?
【发布时间】:2014-01-27 15:10:18
【问题描述】:

我对以下代码的输出感到困惑:

float a  = 1.32;
int* b;
b= &a;
printf("%d", *b);

这段代码把float变成了int,但是我得到的输出是:1068037571

这与计算机的 IEEE 754 转换能力有关吗? 谢谢!

【问题讨论】:

    标签: c floating-point integer type-conversion


    【解决方案1】:

    您的程序调用了未定义的行为。您只是将float 的表示重新解释为int。您绝对不会将int 转换为float。你不能期待任何特定的行为。

    要将 float 转换为 int,您可以使用如下代码:

    int b = a;
    

    float 截断为int

    【讨论】:

    • 但根据我的教授的说法,它具有某种含义,他不会告诉它是什么。它是十六进制表示还是不同的基数?谢谢大卫。
    • 您只是采用浮点a 的按位表示,并将该组位解释为int。并希望这两种数据类型具有相同的大小。编译代码时是否看到编译器警告?
    • @macalaca:如果您的教授在显示的代码之后声明*b 具有含义,那么您的教授要么是错误的,要么是指的是保证这种非标准行为的特定 C 实现。检查编码浮点a 的字节的正确方法是将其表示的字节复制到另一个对象中,例如unsigned int b; memcpy(&b, &a, sizeof b);,或者使用联合,例如unsigned int b = (union {float f; unsigned int u;}){a}.u;。这两个都需要sizeof(float) == sizeof(unsigned int)
    • @macalaca:进一步扩展,这些方法会导致编码浮点对象的字节被重新解释为unsigned int。这样做只是为了检查浮点数的编码,而不是它的值。要使用浮点数的,只需在表达式中正常使用它,包括强制转换和赋值。
    • @macalaca:不要纠结于十六进制与十进制;这是 display 问题,而不是 value 问题。当您收到一个整数时,您只需将其分配给一个浮点变量即可将其转换为浮点数。
    【解决方案2】:

    您正在对代码执行的操作是查看浮点数的存储方式。一个浮点数(通常)在内存中占用 4 个字节,如下(来自维基百科的示例):

    当你运行你的代码时,你假装这些位是一个四字节整数:

    float a  = 1.32;
    int* b;
    b= &a;
    printf("%d", *b);
    

    如果您想查看十六进制表示,只需这样做

    printf("%08x", *b);
    

    你会得到

    3f9d70a4
    

    表示位模式是

    00111111100111010111000010100100
    

    分手:

    0 01111111 00111010111000010100100
    

    符号位0 指数01111111 分数(1)00111010111000010100100

    你会发现那个二进制数

    100111010111000010100100 = 10317988
    

    还有那个

    10317988.0 / (4096.0*2048.0) = 1.23
    

    更新完整的程序,详细说明如何执行此操作:

    #include <stdio.h>
    #include <math.h>
    #include <stdint.h>
    
    int main(void) {
      float a = 1.23;
      uint32_t *b = (uint32_t *)&a;
    
      uint32_t signbit;
      uint32_t exponent;
      uint32_t mantissa;
      uint32_t fpAsInt;
    
      fpAsInt = *b;
    
      signbit = (fpAsInt & 0x80000000) >> 31;
      exponent = (fpAsInt & 0x7F800000) >> 23 ;
      mantissa = (fpAsInt & 0x007FFFFF) | 0x00800000;
      printf("fpAsInt: 0x%08x\n", fpAsInt);
      printf("sign bit: %d\n", signbit);
      printf("exponent: 0x%02x\n",  exponent);
      printf("mantissa: 0x%03x\n",  mantissa);
      printf("the value is %10f\n", ((signbit == 1)?-1.0:1.0)*mantissa / pow(2.0, (127 - exponent + 23)));
      printf("the original value was %10f\n", a);
    }
    

    打印结果

    fpAsInt: 0x3f9d70a4
    sign bit: 0
    exponent: 0x7f
    mantissa: 0x9d70a4
    the value is 1.2300000191
    the original value is 1.2300000191
    

    我在最后一行使用浮点数学似乎是作弊 - 有道理的批评,但这里的重点是展示浮点是如何构造的,而不是如何使用整数数学来提取值。

    注意 - 我假设浮点数是 IEEE 4 字节表示。此外,我还包括了一些特定的转换来消除编译器警告。仅当您确定自己知道自己在做什么时才这样做...

    更多编辑

    有人指出,代码仍然存在未定义的行为。为了解决这个问题,并且仍然让您对上述内容有所了解,让我们再做一次。现在我正在使用位数组和浮点数的联合来“合法地”访问不同的元素 - 并且在使用 -Wall -pedantic 编译时没有收到任何警告。这可能还不够,但这是我所知道的最好的......

    #include <stdio.h>
    #include <math.h>
    #include <stdint.h>
    
    union ieee754_float
      {
        float f;
    
        /* This is the IEEE 754 single-precision format on a little-endian machine.  */
        struct
          {
            unsigned int mantissa:23;
            unsigned int exponent:8;
            unsigned int negative:1;
          } ieee;
        };
    
    int main(void) {
      float a = 1.23;
      union ieee754_float *pa;
      pa = (union ieee754_float*)&a;
    
      uint32_t signbit;
      uint32_t exponent;
      uint32_t mantissa;
    
      signbit = pa->ieee.negative;
      exponent = pa->ieee.exponent;
      mantissa = pa->ieee.mantissa | 0x00800000;
      printf("sign bit: %d\n", signbit);
      printf("exponent: 0x%02x\n",  exponent);
      printf("mantissa: 0x%03x\n",  mantissa);
      printf("the value is %.10f\n", ((signbit == 1)?-1.0:1.0)*mantissa / pow(2.0, (127 - exponent + 23)));
      printf("the original value is %.10f\n", a);
    }
    

    【讨论】:

    • 为什么它“假装这些位是一个 4 字节整数”。这不是假设整数是 32 位的吗?
    • @Brandin - 是的,我假设floatint 都存储在四个字节中。您可以更可靠地编写代码以确保确实如此 - 例如,使用&lt;stdint.h&gt;,然后声明uint32_t
    • 如果您确实需要一个 32 位整数,那么可以。但是我看不出在上述问题中使用 32 位整数如何避免未定义的行为。
    • @Brandin - 查看我更新的代码。你认为这不会总是以同样的方式表现吗?换句话说,我是不是很幸运,这里编写的代码在没有警告的情况下编译,并给出了我期望的结果?或者换句话说 - 我在和 UB 调情吗?我会认为这没问题(假设 IEEE 浮动)。但很高兴发现我错了。
    • 我同意上面评论中的建议-正确的方法是将浮动复制到具有您要使用的类型的相同大小的区域。或者使用离子。即使你的演员似乎在不复制字节的情况下工作,我不认为有一条规则说所有floats 必须以这样一种方式对齐,它们也可以被硬件解释为uint32_t。跨度>
    【解决方案3】:

    StackOverflow 上C 标签中的许多问题都可以通过回到这些基本规则来回答:

    • &amp; 接受一个变量并产生一个指针
    • * 接受一个指针并产生一个变量

    所以你说“取这个浮点变量,让我成为一个指针。现在取那个指针,让我成为一个 int 变量。”你从来没有说过“取这个浮点值并让我成为一个 int 值”。您说“取一个可以转换为浮点变量的指针并将其转换为 int 变量”。当你这样做时会发生什么?这完全由编译器决定。

    【讨论】:

    • 你认为我的 OP 版本的代码还在产生 UB,还是我把它摔倒了?
    • @Floris:您仍在将指向一种变量类型的指针转​​换为完全不同的变量类型。告诉我在 C99 规范的第 6.3.2.3 节中,这种行为被赋予了任何意义。
    • @Floris:另外,请告诉我,在第 6.2.6 节中,您可以按照您的方式提取浮点数的符号、有效数或指数。 C99 标准没有定义浮点数在内存中的布局方式。所有这些都是未定义的行为。当然,通常是 UB 有效,但它是 UB。
    • @Floris 可能比“告诉我在哪里……”更具体。 C99 标准中的 6.5:6 和 6.5:7 条款明确地使表达式 *b 未定义行为,并且它已经有十多年没有“通常工作”了(google “gcc type-based alias analysis”)。 6.3.2.3:7 为(uint32_t *)&amp;a 在某些实现上定义敞开大门:我将把这个条款总结为说明它是实现定义的,这种转换的作用(UB 是选择之一,但不是选择OP 的编译器,除非 OP 要求使用 70 年代的一些奇怪的计算机)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    • 2014-01-16
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多