#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
发生了什么事?为什么打印这些值?我故意这样做,但想了解为什么会这样。详情谢谢!
答案 0 :(得分:22)
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 binary64”格式,binary32表示http://www.binaryconvert.com/convert_double.html格式。您输入的数字将转换为浮点数,只有23位有效值,这意味着数字将近似为:
float
现在我们将它转换为double,它具有53位有效位,我们必须在这些数字的末尾插入30个二进制“0”,以产生例如。
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)
这些主要是为了得出这些数字的实际表示,它们是:
3.299999952316284 = 0b1.10100110011001100110011000000000000000000000000000000 ×2¹
我建议使用little-endian format来查看它如何分解为± m ×2 e 格式。
无论如何,我认为你的系统在正常设置下是x86 / x86_64 / ARM,这意味着数字是使用{{3}}在内存中布局的,所以传递的参数将是
3.3 → 400A6666 60000000
3.4 → 400B3333 40000000
3.5 → 400C0000 00000000
3.6 → 400CCCCC C0000000
4 → 40100000 00000000
5 → 40140000 00000000
在 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"
,所以从中获取4个字节可变输入,即:
int
因此,printf将接收0x60000000,并将其显示为十进制整数,即1610612736,这就是您看到该结果的原因。其他数字可以类似地解释。
byte
#0 #1 ... #4 ... #8 ....
+ - -+ - -+ - -+ - -+ +====+====+====+====+ - -+ - -+ - -+ - -+
: 08 : 10 : 02 : 00 : | 00 | 00 | 00 | 60 | 66 : 66 : 0A : 40 : ....
+ - -+ - -+ - -+ - -+ +====+====+====+====+ - -+ - -+ - -+ - -+
address of "%d" ~~~~~~~~~~~~~~~~~~~
this, as an 'int'
答案 1 :(得分:2)
我假设到目前为止发布的其他答案都没有注意到这一点:我认为你故意使用不同的转换进行扫描和打印,并希望了解结果。如果你确实犯了错误,那么你可以忽略我的答案。
基本上,您需要阅读this article,它将解释如何定义浮点数的位模式,然后写出每个数字的位模式。鉴于您了解整数的存储方式,您应该得到答案。
答案 2 :(得分:0)
您在第二个d
语句中使用的printf
转换说明符需要类型为int
的参数。 C默认参数提升后的参数a
的类型为double
。传递一个与预期不同的类型的参数是一个未定义的行为,并且通常会有未定义的行为,任何事情都可能发生。
答案 3 :(得分:0)
如果您想确切知道发生了什么,请尝试printf('0x%08x\n', a);
而不是printf("%d",a);
。您将能够看到变量a的实际位而不是printf("%d",a);
给出的内容。
答案 4 :(得分:0)
只是因为printf ("%d",a);
:认为a中的内存是一个int,所以它将其内容解释为int。
并且printf("%f",a);
将a的记忆内容视为一个真正的浮动......
但如果你写printf("%d",(int)a)
; // a被转换为int(通过截断的(int)强制转换)。所以打印出a
的近似值。
答案 5 :(得分:0)
在C中,作为参数传递给具有可变数量参数的函数的浮点数被提升为双精度数。这就是为什么在printf函数的格式字符串的引用中,您将看不到浮点数和双精度数的不同格式说明符。因此,当传递给printf时,您的“a”会从32位浮点转换为64位双精度。恰好这样,4和5表示为双精度,64位中的32位为零,而这些零位是printf函数解释为整数的位,因为你告诉它打印一个整数
答案 6 :(得分:0)
printf()
使用第一个参数中提到的格式说明符来解释其可变长度参数。 printf()
的签名如下。
int printf(const char *format, ...);
所以printf()
代码可能会使用stdarg.h
编写。
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
并且此行为未定义!
希望这有帮助!