9

Tôi có dòng này đơn giản mã:Tại sao biến phao tiết kiệm giá trị bằng cách cắt các chữ số sau điểm theo cách kỳ lạ?

float val = 123456.123456; 

khi tôi in val này hoặc tìm trong phạm vi, nó sẽ lưu giá trị 123456,13

Ok, không sao đâu, nó không thể lưu trữ tất cả những chữ số sau thời điểm chỉ trong 4 byte, nhưng tại sao nó làm 13 sau điểm? Không phải là 12?

(sử dụng vC++ 2010 Express trên win32)

+0

cảm ơn tất cả mọi người vì câu trả lời. tôi hiểu rồi. tôi chỉ mặc dù nó sẽ thả các chữ số bị chồng chéo sau điểm, thay vì làm tròn nó. – Kosmos

Trả lời

7

Khi được biểu thị dưới dạng phao, số của bạn có số mũ là 16 (nghĩa là giá trị là số lần mantisse lần 2^16 hoặc 65536). Các mantisse sau đó trở thành

123456.123456/65536 = 1.8837909462890625 

Để phù hợp với một phao 32-bit, các mantisse sẽ bỏ bớt 23 bit, vì vậy bây giờ nó trở nên 1.883791. Khi được nhân lại bởi 65536, số này sẽ trở thành 123456.125.

Lưu ý 5 ở vị trí thứ ba sau dấu thập phân: thường trình đầu ra của C++ mà bạn đã sử dụng làm tròn nó lên, làm cho số cuối cùng của bạn trông giống như 123456.13.

EDIT Giải thích về làm tròn: (comment Rick Regan)

Các tròn xảy ra đầu tiên trong hệ nhị phân (24 bit), trong thập phân để chuyển đổi nhị phân, và sau đó đến chữ số thập phân, trong printf. Giá trị được lưu trữ là 1.1110001001000000001 x 2^16 = 1,8837909698486328125 x 2^16 = 123456.125. Nó in như 123456.13, nhưng chỉ vì Visual C++ sử dụng "round half from zero" làm tròn.

Rick cũng có một số outstanding article on the subject.

Nếu bạn muốn chơi với các số khác và biểu diễn nổi của chúng, đây là very useful IEEE-754 calculator.

+0

Bạn có thể giải thích thêm chút nữa không. Tôi không thể hiểu được đặc biệt dòng này "Khi được biểu diễn dưới dạng phao, số của bạn có số mũ là 16" . Tại sao nó là cần thiết? –

+0

@RasmiRanjanNayak Phao 32 bit được biểu diễn dưới dạng mantisse 23 bit, số mũ nhị phân bảy bit và bit dấu. Về mặt logic, bạn chia số ban đầu của mình cho hai và tăng số mũ cho đến khi chữ số còn lại duy nhất ở phía trước dấu thập phân là '1'. Đối với '123456.123456', cần 16 bộ phận. – dasblinkenlight

+3

@dasblinkenlight Tốt nhất là mô tả này gây hiểu nhầm. Làm tròn xuất hiện đầu tiên trong nhị phân (đến 24 bit), theo thập phân sang chuyển đổi nhị phân, và sau đó thành thập phân, trong printf. Giá trị được lưu trữ là 1.1110001001000000001 x 2^16 = 1,8837909698486328125 x 2^16 = 123456.125. Nó in như 123456.13, nhưng chỉ vì Visual C++ sử dụng "round half away from zero" làm tròn (xem bài viết của tôi http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/.) –

2

Thử in giá trị của std::numeric_limits<float>::digits10. Đây là khoảng nói chính xác bao nhiêu trong cơ sở 10 một phao có. Bạn đang cố gắng vượt quá nó, vì vậy bạn đang trải qua một sự mất chính xác (có nghĩa là chữ số vượt ra ngoài những cái quan trọng không thực sự có ý nghĩa).

Xem ví dụ: What is the meaning of numeric_limits<double>::digits10

2

Hoàn toàn phụ thuộc vào trình biên dịch. Kiểm tra nó trong GCC. Nó phải là xxx.12

+5

Nếu định dạng được sử dụng nội bộ là IEEE-754, nó hoàn toàn không phụ thuộc vào trình biên dịch. –

+0

Kiểm tra http://steve.hollasch.net/cgindex/coding/ieeefloat.html –

+0

Tôi nghĩ rằng chúng tôi đang đưa ra "phụ thuộc vào trình biên dịch" hai ý nghĩa khác nhau; những gì tôi - và những người khác - hiểu là bạn đang nói rằng "nó bình thường mà nó khác nhau từ trình biên dịch đến trình biên dịch". Bạn có nói rằng thay vào đó là một lỗi * trình biên dịch cụ thể *? –

8

Giá trị được lưu trữ trong val bằng 123456.125. Bạn đang nhận được .13 bởi vì bạn đang làm tròn nó:

float val = 123456.123456; 
printf("%.4f %.2f\n", val, val); 

đầu ra: 123456.1250 123456.13

Bạn nên sử dụng đôi trong trường hợp này để tránh cắt ngắn. Trình biên dịch cũng nên cảnh báo bạn: "cảnh báo C4305: 'khởi tạo': cắt từ 'đôi' thành 'phao'".

10

Trong nhị phân, 123456.123456 là 11110001001000000.000111111001 ... (vô hạn). Nó tròn tới 11110001001000000.001 hoặc 123456.125. Đó là vòng đến 123456.13 khi được in.

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