2010-02-08 72 views
15

Các mã sau trong C# (Net 3.5 SP1) là một vòng lặp vô hạn trên máy tính của tôi:C# nổi vòng lặp vô hạn

for (float i = 0; i < float.MaxValue; i++) ; 

Nó vượt quá số lượng 16.777.216,0 và 16.777.216,0 + 1 là đánh giá để 16.777.216,0. Tuy nhiên, vào thời điểm này: i + 1! = I.

Đây là một số sự điên rồ.

Tôi nhận thấy có một số điểm không chính xác về cách lưu số điểm nổi. Và tôi đã đọc rằng số nguyên lớn hơn 2^24 không thể được lưu trữ đúng như một phao.

Vẫn là mã ở trên, phải hợp lệ trong C# ngay cả khi số không thể được biểu diễn đúng.

Tại sao nó không hoạt động?

Bạn có thể nhận được điều tương tự xảy ra cho gấp đôi nhưng phải mất một thời gian rất dài. 9007199254740992.0 là giới hạn cho gấp đôi.

+7

Tại sao bạn sử dụng loại dấu phẩy động cho chỉ mục ở vị trí đầu tiên? – John

+0

Tôi đồng ý rằng nó không phải là mã tốt, nhưng nó không phải là mã đúng? Về mặt kỹ thuật, bất kỳ số nào cộng với một số phải lớn hơn chính nó trừ khi có tràn. – jonathanpeppers

+1

Không nhất thiết. Bạn sẽ lưu ý rằng '16777216.0' là phao chính xác đơn gần nhất với '16777217.0' –

Trả lời

21

phải, vì vậy vấn đề là để thêm một đến phao, nó sẽ phải trở thành

16777217.0 

Nó chỉ như vậy sẽ xảy ra rằng đây là một ranh giới cho radix và không thể được đại diện chính xác như một phao. (Giá trị cao nhất tiếp theo có sẵn là 16777218.0)

Vì vậy, nó vòng vào phao biểu diễn gần

16777216.0 

Hãy để tôi đặt nó theo cách này:

Vì bạn có một nổi lượng chính xác , bạn phải tăng lên theo số cao hơn và cao hơn.

EDIT:

Ok, đây là một chút khó khăn để giải thích, nhưng cố gắng này:

float f = float.MaxValue; 
f -= 1.0f; 
Debug.Assert(f == float.MaxValue); 

này sẽ chạy tốt, bởi vì ít giá trị, để đại diện cho một sự khác biệt của 1.0f, bạn sẽ cần hơn 128 bit chính xác. Một phao chỉ có 32 bit.

EDIT2

Theo tính toán của tôi, ít nhất 128 chữ số nhị phân unsigned sẽ là cần thiết.

log(3.40282347E+38) * log(10)/log(2) = 128 

Là giải pháp cho vấn đề của bạn, bạn có thể lặp qua hai số 128 bit. Tuy nhiên, điều này sẽ mất ít nhất một thập kỷ để hoàn thành.

+0

C# nên cho phép nó được tăng lên, mặc dù phải không? Bất kỳ float + 1 nào phải lớn hơn float, phải không? i + 1> i trong mọi trường hợp? Tôi sẽ nói nếu không có một tràn, nhưng số lượng là hư không gần float.MaxValue. – jonathanpeppers

+6

@ Jonathan.Peppers: Tôi không chắc chắn nơi bạn có quan niệm sai lầm này về các hệ thống số dấu phẩy động. Tôi đề nghị bạn đọc về chúng trên wikipedia, sau đó đánh giá lại mã của bạn. http://en.wikipedia.org/wiki/Floating_point – Welbog

+3

Ok, hãy nghĩ về nó theo cách này: Nổi có số chữ số được đặt. Lên gần 'float.MaxValue', giá trị không chính xác. Trong thực tế, hầu hết các chữ số không đáng kể được cắt ngắn. Hãy thử trừ một từ float.MaxValue. –

-4

Lặp lại khi tôi tiếp cận float.MaxValue có i ngay bên dưới giá trị này. Lần lặp tiếp theo thêm vào i, nhưng nó không thể giữ một số lớn hơn float.MaxValue. Vì vậy, nó giữ một giá trị nhỏ hơn nhiều, và bắt đầu lặp lại một lần nữa.

+0

Mã không bị tràn ... – TJMonk15

6

Để hiểu những gì đang xảy ra sai bạn sẽ phải đọc các tiêu chuẩn IEEE trên floating point

Hãy kiểm tra cấu trúc của một số floating point trong một giây:

Một số dấu chấm động được chia thành hai các bộ phận (ok 3, nhưng bỏ qua bit dấu hiệu trong một giây).

Bạn có số mũ và số mũ. Giống như vậy:

smmmmmmmmeeeeeee 

Lưu ý: điều đó không chính xác với số bit, nhưng nó cung cấp cho bạn một ý tưởng chung về những gì đang xảy ra.

Để tìm ra những gì bạn có số chúng tôi làm việc tính toán sau:

mmmmmm * 2^(eeeeee) * (-1)^s 

Vì vậy, những gì đang float.MaxValue sẽ? Vâng, bạn sẽ có mantissa lớn nhất có thể và số mũ lớn nhất có thể. Hãy giả vờ này trông giống như sau:

01111111111111111 

trong thực tế chúng ta định nghĩa NAN và + -INF và một vài công ước khác, nhưng bỏ qua chúng trong một giây bởi vì họ không liên quan đến câu hỏi của bạn.

Vì vậy, điều gì xảy ra khi bạn có 9.9999*2^99 + 1? Vâng, bạn không có đủ số liệu quan trọng để thêm 1. Kết quả là nó được làm tròn xuống cùng một số. Trong trường hợp độ chính xác của dấu phẩy động, điểm mà tại đó +1 bắt đầu làm tròn xuống sẽ xảy ra là 16777216.0

8

Hãy tưởng tượng ví dụ rằng một số dấu phẩy động được biểu thị bằng 2 chữ số thập phân đáng kể, cộng với số mũ: trường hợp, bạn có thể đếm từ 0 đến 99 chính xác. Tiếp theo sẽ là 100, nhưng bởi vì bạn chỉ có thể có 2 chữ số có nghĩa sẽ được lưu trữ là "1,0 lần 10 đến sức mạnh của 2". Thêm một vào đó sẽ là ... cái gì?

Tốt nhất, nó sẽ là 101 là kết quả trung gian, thực tế sẽ được lưu trữ (thông qua lỗi làm tròn loại bỏ số thứ ba không đáng kể) là "1,0 lần 10 đến lũy thừa của 2" một lần nữa.

2

Không có gì liên quan đến tràn hoặc đang ở gần giá trị tối đa. Giá trị float cho 16777216.0 có một đại diện nhị phân của 16777216. Sau đó bạn tăng nó bằng 1, vì vậy nó phải là 16777217.0, ngoại trừ biểu diễn nhị phân của 16777217.0 là 16777216 !!! Vì vậy, nó không thực sự được tăng lên hoặc ít nhất là tăng không làm những gì bạn mong đợi.

Đây là một lớp được viết bởi Jon Skeet minh họa này:

DoubleConverter.cs

Hãy thử mã này với nó:

double d1 = 16777217.0; 
Console.WriteLine(DoubleConverter.ToExactString(d1)); 

float f1 = 16777216.0f; 
Console.WriteLine(DoubleConverter.ToExactString(f1)); 

float f2 = 16777217.0f; 
Console.WriteLine(DoubleConverter.ToExactString(f2)); 

Chú ý cách trình bày nội bộ của 16.777.216,0 là như nhau 16.777.217,0! !

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