C语言中浮点类型的整数值

10
#include<stdio.h>
 int main()
 {
   float a;
   printf("Enter a number:");
   scanf("%f",&a);
   printf("%d",a);
   return 0;
 }

我在Ubuntu中使用gcc运行程序。

对于数值--

          3.3 it gives value 1610612736 
          3.4 it gives value 1073741824
          3.5 it gives value 0
          3.6 it gives value -1073741824
          4 it gives value 0
          5 it gives value 0

发生了什么?为什么会打印出这些值?我是有意为之的,但我想知道为什么会这样。请提供详细信息!

7个回答

23

printf函数不知道您传入的格式类型,因为这部分是可变的。

int printf(const char* format, ...);
//                             ^^^
在C标准中,传递一个float类型的参数将自动提升为double(C11§6.5.2.2/6),在调用方不会进行其他操作。
printf内部,由于它不知道那个...东西的类型(§6.7.6.3/9),因此必须使用来自其他地方的提示——格式字符串。由于您传递了"%d",它告诉函数,期望一个int类型的参数。
根据C标准,这会导致未定义的行为(§7.21.6.1/8-9),其中包括打印一些奇怪的数字,结束故事。
但是,实际上发生了什么? 在大多数平台上,double采用"IEEE 754二进制64位浮点格式"表示,而float则采用二进制32位浮点格式。您输入的数字被转换为浮点数,在这里只有23位有效数字,这意味着数字将像这样近似:
3.3 ~ (0b1.10100110011001100110011) × 2¹  (actually: 3.2999999523162842...)
3.4 ~ (0b1.10110011001100110011010) × 2¹  (actually: 3.4000000953674316...)
3.5 = (0b1.11                     ) × 2¹  (actually: 3.5)
3.6 ~ (0b1.11001100110011001100110) × 2¹  (actually: 3.5999999046325684...)
4   = (0b1                        ) × 2²  (actually: 4)
5   = (0b1.01                     ) × 2²  (actually: 5)

现在我们将其转换为double类型,它具有53位的有效数字,我们必须在这些数字后面插入30个二进制“0”,以产生例如:

3.299999952316284 = 0b1.10100110011001100110011000000000000000000000000000000 ×2¹

这些主要是为了推导那些数字的实际表示,它们是:

3.3 → 400A6666 60000000
3.4 → 400B3333 40000000
3.5 → 400C0000 00000000
3.6 → 400CCCCC C0000000
4   → 40100000 00000000
5   → 40140000 00000000

建议使用http://www.binaryconvert.com/convert_double.html来查看这个数值是如何转换为±m × 2e格式的。

不过,我猜你的系统通常是x86/x86_64/ARM,这意味着内存中的数据采用小端序格式存储。因此传递的参数将是这样的:

 byte
  #0   #1   ...          #4   ...            #8 ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
| 08 | 10 | 02 | 00 |  | 00 | 00 | 00 | 60 | 66 | 66 | 0A | 40 | ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
 address of "%d"         content of 3.299999952316284
 (just an example)

printf 内部,它会使用格式化字符串 "%d",解析该字符串,然后通过 %d 发现需要一个 int,因此会从可变输入中获取 4 个字节,也就是:

 byte
  #0   #1   ...          #4   ...            #8 ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
: 08 : 10 : 02 : 00 :  | 00 | 00 | 00 | 60 | 66 : 66 : 0A : 40 : ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
 address of "%d"        ~~~~~~~~~~~~~~~~~~~
                        this, as an 'int'
所以,printf将接收到0x60000000,并将其显示为十进制整数,即1610612736,这就是您看到该结果的原因。其他数字也可以用类似的方式解释。
3.3 → ... 60000000 = 1610612736
3.4 → ... 40000000 = 1073741824
3.5 → ... 00000000 = 0
3.6 → ... C0000000 = -1073741824 (note 2's complement)
4   → ... 00000000 = 0
5   → ... 00000000 = 0

为了完整起见,以下是一些有用的二进制补码链接:二进制补码-维基百科二进制补码笔记- Thomas Finley - mctylr

2
我假设目前发布的其他答案都没有理解重点:我认为你是故意在扫描和打印时使用不同的转换方式,并想了解结果。如果确实是你犯了一个错误,那么你可以忽略我的回答。
基本上,你需要阅读这篇文章,它将解释浮点数的位模式是如何定义的,然后写出每个数字的位模式。考虑到你已经了解整数的存储方式,你应该就有了答案。

0
你在第二个printf语句中使用的d转换说明符需要一个int类型的参数。经过C默认参数推广后,你的参数adouble类型的。传递一个与期望类型不同的参数是一种未定义行为,和通常的未定义行为一样,任何事情都有可能发生。

0
如果你想确切地知道发生了什么,请尝试使用printf('0x%08x\n', a);而不是printf("%d",a);。这样你就可以看到变量a的实际位,而不是printf("%d",a);给你的结果。

0

仅仅因为 printf ("%d",a);:认为 a 中的内存是 int 类型,因此将其内容解释为 int。

printf("%f",a); 将 a 的内存内容视为 float,而它确实是 float...

但是如果你写 printf("%d",(int)a); // a 被转换为 int(通过 (int) 强制类型转换并截断)。因此,a 的近似值被打印出来。


0
在C语言中,作为可变参数传递给函数的浮点数会被提升为双精度浮点数。这就是为什么在printf函数的格式字符串参考中,你不会看到针对浮点数和双精度浮点数的不同格式说明符。因此,当将你的“a”传递给printf时,它会从32位浮点数转换为64位双精度浮点数。恰好4和5以这样一种方式表示为双精度浮点数,其中64位中的32位是零,而这些零位是printf函数解释为整数的位,因为你告诉它要打印一个整数。

0

printf()函数使用在第一个参数中指定的格式说明符来解释其可变长度的参数。 printf()函数的签名如下所示。

int printf(const char *format, ...);

所以使用 stdarg.hprintf() 代码可能会像这样编写。
int printf(const char *format, ...) {
    va_list ap;
    char *p, *sval;
    int ival;
    float fval;

    va_start(ap, format);
    for(p=format; *p ; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        switch(*++p) {
            case 'd':
                ival = va_arg(ap, int);
                break;

            case 'f':
                fval = va_arg(ap, float);
                break;

            case 's':
                for (sval = va_arg(ap, char *); *sval; sval++);
                break;

            default:
                putchar(*p);
                break;
        }
    }
    va_end(ap);
}

如果你将%d传递给一个float,那么你可以想象一下在printf()内部会发生什么。 printf()将把float变量解释为int,这种行为是未定义的!

希望这可以帮助你!


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