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

Tôi đang chạy chương trình với gcc trong Ubuntu. Đối với các giá trị--Giá trị nguyên của loại thả nổi trong 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 

Điều gì đang xảy ra? Tại sao các giá trị này được in? Tôi đang cố ý làm điều này, nhưng muốn hiểu tại sao điều này lại xảy ra. Chi tiết được đánh giá cao!

Trả lời

22

Chức năng printf không biết loại định dạng bạn đã chuyển, vì phần đó là variadic.

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

Trong tiêu chuẩn C, đi qua một float sẽ tự động thăng một double (C11§6.5.2.2/6), và không có gì khác sẽ được thực hiện ở phía người gọi.

Bên trong printf, vì nó không biết loại ... thingie (§6.7.6.3/9), nó phải sử dụng gợi ý từ nơi khác - chuỗi định dạng. Vì bạn đã vượt qua "%d", nó đang cho biết chức năng đó, một dự kiến ​​là int.

Theo tiêu chuẩn C, điều này dẫn đến hành vi không xác định (§7.21.6.1/8–9), bao gồm khả năng in một số số lạ, kết thúc câu chuyện.

Nhưng điều gì đang thực sự xảy ra? Trong hầu hết các nền tảng, một double được thể hiện dưới dạng "IEEE 754 binary64" và định dạng float ở định dạng binary32. Những con số mà bạn đã nhập không chuyển đổi sang một phao, mà chỉ có 23 bit có ý nghĩa, có nghĩa là những con số sẽ được xấp xỉ như thế này:

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) 

Bây giờ chúng ta chuyển đổi này sẽ tăng gấp đôi, trong đó có 53 bit có ý nghĩa , chúng ta phải chèn 30 số nhị phân "0" vào cuối những con số này, để tạo ra ví dụ

3.299999952316284 = 0b1.10100110011001100110011000000000000000000000000000000 ×2¹ 

Đây là chủ yếu để lấy được các đại diện thực tế của những con số, đó là:

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

tôi khuyên bạn nên sử dụng http://www.binaryconvert.com/convert_double.html để xem cách này phá vỡ xuống ± m × 2 e Định dạng.

Dù sao, tôi cho rằng hệ thống của bạn là một x86/x86_64/ARM trong khung cảnh bình thường, có nghĩa là con số được đưa ra trong bộ nhớ sử dụng little-endian format, vì vậy các đối số được truyền sẽ như thế nào

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) 

Bên trong printf , nó tiêu thụ các chuỗi định dạng "%d", phân tích nó, và sau đó phát hiện ra rằng một int là cần thiết vì% d, vì vậy 4 byte được lấy từ đầu vào variadic, đó là:

byte 
    #0 #1 ...   #4 ...   #8 .... 
+ - -+ - -+ - -+ - -+ +====+====+====+====+ - -+ - -+ - -+ - -+ 
: 08 : 10 : 02 : 00 : | 00 | 00 | 00 | 60 | 66 : 66 : 0A : 40 : .... 
+ - -+ - -+ - -+ - -+ +====+====+====+====+ - -+ - -+ - -+ - -+ 
address of "%d"  ~~~~~~~~~~~~~~~~~~~ 
         this, as an 'int' 

vậy, printf sẽ nhận được 0x60000000 và hiển thị nó dưới dạng số nguyên thập phân, là 1610612736, đó là lý do tại sao bạn thấy kết quả đó. Các số khác có thể được giải thích tương tự.

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 dành thời gian để viết tất cả điều này. :) – Mysticial

+0

Để hoàn thiện, một vài liên kết bổ sung hữu ích của Hai, [Hai bổ sung - Wikipedia] (http://en.wikipedia.org/wiki/Two%27s_complement) và [Hai bổ sung ghi chú Thomas Finley] (http: // tfinley.net/notes/cps104/twoscomp.html). – mctylr

2

Tôi giả định rằng các câu trả lời khác được đăng xa bị thiếu điểm: Tôi nghĩ rằng bạn đang cố tình sử dụng các chuyển đổi khác nhau để quét và in và muốn hiểu kết quả. Nếu, thực sự, bạn vừa phạm sai lầm, thì bạn có thể bỏ qua câu trả lời của tôi.

Về cơ bản, bạn cần đọc this article, điều này sẽ giải thích cách thức các mẫu bit cho số dấu phẩy động được xác định, sau đó viết ra các mẫu bit cho từng số đó. Cho rằng bạn hiểu cách số nguyên được lưu trữ, sau đó bạn sẽ có câu trả lời của bạn.

0

Số tham chiếu chuyển đổi d bạn sử dụng trong câu lệnh printf thứ hai của bạn yêu cầu đối số loại int. Lập luận của bạn a sau khi xúc tiến lập luận C mặc định là loại double. Vượt qua một đối số của một kiểu khác nhau mà người ta mong đợi là một hành vi không xác định và như thường lệ với hành vi không xác định, bất cứ điều gì cũng có thể xảy ra.

0

Nếu bạn muốn biết chính xác những gì đang diễn ra, hãy thử printf('0x%08x\n', a); thay vì printf("%d",a);. Bạn sẽ có thể để xem các bit thực tế của biến một thay vì những gì printf("%d",a); ban cho ngươi.

0

đơn giản chỉ vì printf ("%d",a);: nghĩ rằng bộ nhớ trong là một int để nó giải thích nội dung của nó như là một int. và printf("%f",a); xem xét nội dung của bộ nhớ của một phao mà nó thực sự là ...

nhưng nếu bạn viết printf("%d",(int)a); // a được chuyển thành một int (by (int) cast với truncation). do đó giá trị approximativ của a được in.

0

Trong C, trôi đi qua như các đối số chức năng với số biến của tham số được thăng tiến để tăng gấp đôi. Đó là lý do tại sao trong tham chiếu đến chuỗi định dạng của hàm printf, bạn sẽ không thấy các định dạng khác nhau cho phao nổi và cho đôi. Vì vậy, "a" của bạn được chuyển đổi từ một phao 32 bit sang một đôi 64 bit khi được chuyển tới printf. Nó chỉ như vậy sẽ xảy ra rằng 4 và 5 được biểu diễn dưới dạng tăng gấp đôi trong một cách mà 32 trong số 64 bit là số không, và những số không bit là những người mà chức năng printf giải thích như một số nguyên, kể từ khi bạn nói với nó để in một số nguyên .

0

printf() diễn giải (các) đối số độ dài biến của nó bằng cách sử dụng định dạng thông số đã được đề cập trong tham số đầu tiên. Chữ ký của printf() như sau.

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

Vì vậy, mã printf() có lẽ được viết như thế này sử dụng 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); 
} 

Vì vậy, nếu bạn vượt qua %d cho một float sau đó bạn có thể tìm ra những gì sẽ xảy ra bên trong printf().printf() sẽ diễn giải một biến số float dưới dạng int và hành vi này không xác định!

Hy vọng điều này sẽ hữu ích!

Các vấn đề liên quan