2012-03-14 41 views
9

hoạt động này trả về một 0:Đây có phải là lỗi không? hoạt động phao được coi là số nguyên

string value = “0.01”; 
float convertedValue = float.Parse(value); 
return (int)(convertedValue * 100.0f); 

Nhưng hoạt động này trả về một 1:

string value = “0.01”; 
float convertedValue = float.Parse(value) * 100.0f; 
return (int)(convertedValue); 

Vì convertedValue là một float, và nó là trong ngoặc * 100f shouldn nó vẫn được coi là hoạt động nổi?

+0

Bạn đang sử dụng phiên bản C# nào? Tôi không thể tái tạo điều này. Tôi nghi ngờ nó có lẽ không xảy ra như bạn mô tả, bởi vì thực sự hai hoạt động đó sẽ trả về cùng một kết quả. –

+0

Đó là trong mã cho một trang web asp.net. Tôi tin rằng nó được cấu hình để chạy .net 4 nhưng không chắc chắn. – Mark1270287

+0

Tôi đánh giá cao tất cả các câu trả lời. Tôi muốn tôi có thể ghi nhận một số trong số họ. – Mark1270287

Trả lời

19

Sự khác biệt giữa hai phương pháp nằm trong cách trình biên dịch tối ưu hóa các hoạt động điểm động. Hãy để tôi giải thích.

string value = "0.01"; 
float convertedValue = float.Parse(value); 
return (int)(convertedValue * 100.0f); 

Trong ví dụ này, giá trị được phân tích thành số dấu phẩy động 80 bit để sử dụng trong các dấu phẩy động bên trong của máy tính. Sau đó, điều này được chuyển thành phao 32 bit để lưu trữ trong biến số convertedValue. Điều này làm cho giá trị được làm tròn đến, dường như, một số hơi nhỏ hơn 0,01. Sau đó, nó được chuyển đổi trở lại một phao 80-bit và nhân với 100, làm tăng lỗi làm tròn gấp 100 lần. Sau đó, nó được chuyển đổi thành int 32 bit. Điều này làm cho phao được rút ngắn, và vì nó thực sự là hơi nhỏ hơn 1, việc chuyển đổi int trả về 0.

string value = "0.01"; 
float convertedValue = float.Parse(value) * 100.0f; 
return (int)(convertedValue); 

Trong ví dụ này, giá trị được phân tách thành một 80-bit số dấu chấm động một lần nữa. Sau đó nó được nhân với 100, trước khi nó được chuyển thành phao 32 bit.Điều này có nghĩa là lỗi làm tròn quá nhỏ đến nỗi khi nó được chuyển thành phao 32 bit để lưu trữ trong convertedValue, nó tròn thành chính xác 1. Khi nó được chuyển thành một int, bạn nhận được 1.

ý tưởng là máy tính sử dụng các phao nổi chính xác cao để tính toán và sau đó làm tròn các giá trị bất cứ khi nào chúng được lưu trữ trong một biến. Bạn càng có nhiều bài tập với phao, càng có nhiều lỗi làm tròn.

+0

"Sau đó, điều này được chuyển đổi sang một float 32-bit cho lưu trữ trong biến conversionValue." Tôi không tin rằng điều này được đảm bảo xảy ra. Nó có thể (đặc biệt là không có tối ưu hóa) được biên dịch như vậy đôi khi, nhưng trình biên dịch là miễn phí để điều trị giữ giá trị ở 80 bit. Nếu bạn đọc thông số CLI, lưu trữ trong một biến cục bộ không bắt buộc giảm độ chính xác. – CodesInChaos

+0

@CodeInChaos: Bạn đã đúng. Trong trường hợp này, việc tối ưu hóa dường như không xảy ra, có thể vì nó là một bản dựng gỡ lỗi. Nếu tối ưu hóa đã xảy ra, hai đoạn mã sẽ có hiệu quả giống hệt nhau. –

+4

Đây là câu trả lời gương mẫu; Tôi sẽ chỉ echo điểm của CodeInChaos rằng các trình biên dịch C# và JIT được phép thực hiện hoặc không thực hiện tối ưu hóa này * hoàn toàn tại whim * của chúng, và về cơ bản nó đi xuống đến các chi tiết của lịch trình đăng ký. CodeInChaos cũng ghi chú chính xác rằng việc truyền tới float sẽ vô hiệu hóa tối ưu hóa. –

14

Vui lòng đọc phần giới thiệu về điểm nổi. Đây là một vấn đề điểm nổi điển hình. Điểm nổi nhị phân không thể đại diện chính xác cho 0.01.

0.01 * 100 xấp xỉ 1.

Nếu nó xảy ra để được làm tròn đến 0.999... bạn nhận được 0, và nếu nó được làm tròn đến 1.000... bạn nhận được 1. Mà một trong những bạn nhận được là không xác định.

Trình biên dịch jit không bắt buộc phải tròn theo cùng một cách mỗi lần gặp một biểu thức tương tự (hoặc thậm chí cùng một biểu thức trong các ngữ cảnh khác nhau). Đặc biệt nó có thể sử dụng độ chính xác cao hơn bất cứ khi nào nó muốn, nhưng có thể hạ cấp xuống 32 bit nổi nếu nó nghĩ đó là một ý tưởng hay.


Một điểm thú vị là một diễn viên rõ ràng để float (ngay cả khi bạn đã có một biểu hiện của loại float). Điều này buộc JITer giảm độ chính xác xuống 32 bit ở thời điểm đó. Việc làm tròn chính xác vẫn chưa được xác định.

Kể từ khi làm tròn không xác định, nó có thể khác nhau giữa các phiên bản .net, gỡ lỗi/phát hành xây dựng, sự hiện diện của debuggers (và có thể là giai đoạn của mặt trăng: P).

Vị trí lưu trữ cho số dấu phẩy động (thống kê, phần tử mảng và trường lớp) có kích thước cố định. Các kích thước lưu trữ được hỗ trợ là float32 và float64. Ở mọi nơi khác (trên ngăn xếp đánh giá, làm đối số, dưới dạng loại trả về và làm biến cục bộ) số dấu phẩy động được biểu diễn bằng cách sử dụng loại dấu phẩy động bên trong .

Khi giá trị dấu phẩy có đại diện bên trong có phạm vi và/hoặc độ chính xác lớn hơn loại danh nghĩa được đặt ở vị trí lưu trữ, nó sẽ tự động bị ép buộc vào loại vị trí lưu trữ. Điều này có thể liên quan đến việc mất chính xác hoặc tạo ra giá trị ngoài phạm vi (NaN, + vô cực hoặc vô cực). Tuy nhiên, giá trị có thể được giữ lại trong biểu diễn bên trong để sử dụng trong tương lai, nếu nó được tải lại từ vị trí lưu trữ mà không có đã được sửa đổi. Đó là trách nhiệm của trình biên dịch để đảm bảo rằng giá trị giữ lại vẫn còn hiệu lực tại thời điểm tải tiếp theo, có tính đến các hiệu ứng của răng cưa và các luồng thực hiện khác (xem mô hình bộ nhớ (§12.6)). Tuy nhiên, quyền tự do mang thêm độ chính xác này không được phép, sau khi thực hiện chuyển đổi rõ ràng (conv.r4 hoặc conv.r8), tại thời điểm đó, biểu diễn bên trong phải là chính xác có thể thể hiện trong loại được liên kết.


vấn đề cụ thể của bạn có thể được giải quyết bằng cách sử dụng Decimal, nhưng vấn đề tương tự với 3*(1/3f) sẽ không được giải quyết bằng cách này, vì Decimal không thể đại diện một phần ba chính xác một trong hai.

+4

Bạn nên nhấn mạnh thực tế rằng đây là * không * một lỗi. – Gabe

+1

Được rồi, tôi hiểu rằng phao có thể có lỗi làm tròn, nhưng tại sao nó lại tròn khác nhau trong khối đầu tiên từ thứ hai? – Mark1270287

+2

@ user1270287 vì có thể. Trình biên dịch jit là miễn phí để sử dụng độ chính xác cao hơn nếu nó muốn, nhưng nó không bắt buộc. Đôi khi nó có, đôi khi nó không. – CodesInChaos

2

Trong dòng này:

(int)(convertedValue * 100.0f) 

Giá trị trung gian là thực sự chính xác cao hơn, chứ không phải chỉ đơn giản là một phao. Để có được kết quả giống với một thứ hai, bạn sẽ phải làm:

(int)((float)(convertedValue * 100.0f)) 

Ở cấp độ IL, sự khác biệt trông giống như:

mul 
    conv.i4 

so với phiên bản thứ hai của bạn:

mul 
    stloc.3 
    ldloc.3 
    conv.i4 

Lưu ý rằng cửa hàng thứ hai/khôi phục giá trị trong biến số float32, buộc nó phải có độ chính xác là float. (Lưu ý rằng, theo bình luận CodeInChaos', điều này không được bảo đảm bởi spec.)

(Đối với đầy đủ các diễn viên rõ ràng trông giống như :)

mul 
    conv.r4 
    conv.i4 
+0

Giá trị trung gian là một 'float' theo như C# là có liên quan. Và theo như CLR có liên quan, nó có thể sử dụng bất kỳ độ chính xác nào bằng hoặc cao hơn 'float'. Thông thường đó là một phao 80bit. (Tôi không phải là người downvoter) – CodesInChaos

+0

Cửa hàng/tải có buộc nó vào 'float' không? Bạn có một tham chiếu cho điều đó? – CodesInChaos

+0

@CodeInChaos: Tôi * giả sử * nó hoạt động giống như trong C - tức là nếu bạn không ép buộc các cửa hàng/tải (ví dụ: với '-loại-cửa hàng') bạn sẽ có được độ chính xác vượt quá. Tôi sẽ cố gắng xem liệu điều này có được ghi chép hay không. – porges

-1

Tôi biết vấn đề này và alwayes làm việc với nó. Khi bạn bè CodeInChaose của chúng tôi trả lời rằng điểm nổi sẽ không được hiển thị trên bộ nhớ như của nó.

Nhưng tôi muốn thêm rằng bạn có lý do cho kết quả khác, không phải vì JIT tự do sử dụng độ chính xác mà anh ấy muốn.

Lý do là trên mã đầu tiên bạn đã chuyển đổi chuỗi và lưu nó vào bộ nhớ để trường hợp này nó sẽ không được lưu 0,1 và một số cách sẽ được lưu 0.0999966 hoặc một cái gì đó giống như số này.

Trên mã thứ hai, bạn thực hiện chuyển đổi và trước khi bạn lưu nó vào bộ nhớ và trước khi giá trị được cấp phát trên bộ nhớ bạn đã thực hiện phép nhân, do đó bạn sẽ có kết quả chính xác mà không gặp rủi ro về số chính xác JIT.

+0

Tôi không nghĩ rằng lý do của bạn là hợp lệ, ít nhất là khi tối ưu hóa được bật. – CodesInChaos

+0

Iam làm việc trên 4 ngôn ngữ (.Net, C++, JAVA, ObjectiveC) tất cả đều có cùng một kết quả. nếu giá trị được phân bổ trên biến bộ nhớ thì kết quả cuối cùng của bạn là không mong muốn và nếu bạn thực hiện phép toán * hoặc/sau đó gán giá trị cho biến được phân bổ, bạn sẽ có kết quả chính xác ... –

+0

Chuyển nhượng cho biến cục bộ không được đảm bảo để có bất kỳ tác dụng trên. Biến cục bộ không phải là vị trí lưu trữ. Và nếu đó là một vị trí lưu trữ, trình biên dịch sẽ tự do giữ độ chính xác cao hơn, nếu nó có thể đảm bảo rằng vị trí lưu trữ được đọc lại không thay đổi. Nếu bạn có mâu thuẫn với thông tin, vui lòng trích dẫn các nguồn của bạn (thông số kỹ thuật). | Bản thân C++ không đảm bảo bất cứ điều gì liên quan đến làm tròn điểm nổi. Với hầu hết các trình biên dịch, nó phụ thuộc vào các cờ bạn đã đặt. | Không có ý tưởng về JAVA và ObjectiveC. – CodesInChaos

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