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

我在Ubuntu中运行程序gcc。 对于数值 -C中浮点类型的整数值

  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 

发生了什么事?为什么打印这些值?我故意这样做,但想知道为什么会发生这种情况。细节非常感谢!

回答

22

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

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

在C标准,传递一个float会自动提升为double(C11§6.5.2.2/ 6),而不是其他将在发送方完成。

printf里面,因为它不知道... thingie(§6.7.6.3/ 9)的类型,所以它必须使用其他地方的提示 - 格式字符串。既然你已经通过了"%d",它正在告诉函数,预计有一个int

根据C标准,这导致了未定义的行为(§7.21.6.1/ 8-9),其中包括打印一些奇怪的数字,故事结束的可能性。

但是究竟发生了什么?在大多数平台中,double被表示为“IEEE 754 binary64”格式,并且binary32格式表示float。您所输入的号码将被转换为浮动,只有具有意义的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) 

现在我们将它转​​换为加倍,它具有意义的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看不出这分解到±×2 ë格式。

不管怎样,我想你的系统是在正常设置在x86/x86_64的/ ARM,这意味着数字使用little-endian format在内存中摆出来,所以传递的参数会像

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",解析它,然后找出该一个int需要,因为%d,所以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 
+0

+1需要时间写出所有这些。 :) – Mysticial 2011-12-29 19:29:27

+0

为了完整起见,有两个有用的补码链接,[Two's Complement - Wikipedia](http://en.wikipedia.org/wiki/Two%27s_complement)和[Two's Complement notes Thomas Finley](http:// tfinley。净/笔记/ cps104/twoscomp.html)。 – mctylr 2011-12-29 20:04:43

2

我假设现在发布的其他答案缺少重点:我认为你故意使用不同的转换进行扫描和打印,并且想要了解结果。如果你确实犯了一个错误,那么你可以忽略我的答案。

基本上,你需要阅读this article,这将解释如何定义浮点数的位模式,然后写出每个数字的位模式。鉴于你了解整数如何存储,你应该得到你的答案。

0

d您在第二个printf语句中使用的转换说明符需要参数类型为int。在C默认参数提升后,您的参数a的类型为double。传递一个不同类型的参数是一个未定义的行为,并且像往常一样未定义的行为,任何事情都可能发生。

0

如果您想知道到底发生了什么,请尝试printf('0x%08x\n', a);而不是printf("%d",a);。你将能够看到变量a的实际位,而不是printf("%d",a);给你的东西。

0

只是因为printf ("%d",a);:认为a中的内存是一个int,因此它将其内容解释为int。 和printf("%f",a);考虑一个内存的内存为一个浮动它是真的...

但如果你写printf("%d",(int)a); // a被转换为int(通过(int)强制转换)。所以打印出a的近似值。

0

在C中,浮点数作为参数传递给具有可变数量的参数的函数被提升为双精度。这就是为什么在printf函数的格式字符串的引用中,您不会看到浮点数和双精度的不同格式说明符。所以,当传递给printf时,你的“a”从32位浮点数转换为64位双精度浮点数。恰巧4和5被表示为双精度,64位中有32位是零,而这些零位是printf函数解释为整数的那些位,因为您告诉它打印一个整数。

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); 
} 

所以,如果你通过%dfloat那么你可以找出printf()里面会发生什么。printf()将把float变量解释为int,并且此行为未定义!

希望这会有所帮助!