2012-06-05 24 views
10

So sánh một số mã C và F # Tôi đang cố gắng thay thế bằng, tôi quan sát thấy có một số khác biệt trong kết quả cuối cùng.C có đôi khác với .NET không?

Làm việc sao lưu mã, tôi phát hiện ra rằng ngay cả ở đó cũng có sự khác biệt - mặc dù có những khác biệt nhỏ.

Mã bắt đầu bằng cách đọc dữ liệu từ tệp. và con số đầu tiên xuất hiện khác nhau. Ví dụ: trong F # (dễ viết hơn):

let a = 71.9497985840 
printfn "%.20f" a 

Tôi nhận được kết quả mong đợi (cho tôi) 71.94979858400000000000.

Nhưng trong C:

a = 71.9497985840; 
fprintf (stderr, "%.20f\n", a); 

in ra 71.94979858400000700000.

7 từ đó đến từ đâu?

Sự khác biệt chỉ nhỏ, nhưng nó làm phiền tôi vì tôi không biết tại sao. (Nó cũng làm phiền tôi bởi vì nó làm cho nó khó khăn hơn để theo dõi nơi hai phiên bản mã của tôi đang phân kỳ)

+0

Bạn có chắc chắn cả hai giá trị đều tăng gấp đôi? Bạn có chắc là lỗi không được in? – Euphoric

+1

số học dấu chấm động trong .net không có kết quả chính xác được xác định rõ. Nó chỉ là một xấp xỉ. Các chữ số thấp hơn có thể thay đổi theo ý thích của trình biên dịch và JITer, và chỉ quan sát chúng với một cái gì đó như 'printfn' có thể ảnh hưởng đến chúng. – CodesInChaos

+0

Sử dụng trình gỡ lỗi hoặc thứ gì đó để xem mẫu bit chính xác của các biến. Nếu cả triển khai F # và C bạn sử dụng cùng một định dạng dấu phẩy động và các biến có cùng một mẫu bit, thì sự khác nhau là trong bản in hoặc cách trình biên dịch phân tích chữ nổi. Trong cả hai trường hợp, vì các dấu phẩy động chỉ là các phép tính xấp xỉ, nó có thể không có gì phải lo lắng, vì sự khác biệt là khá nhỏ. –

Trả lời

7

Đó là một sự khác biệt trong in ấn. Chuyển đổi giá trị đó để một IEEE754 double mang

Prelude Text.FShow.RealFloat> FD 71.9497985840 
71.94979858400000694018672220408916473388671875 

nhưng đại diện 71.949798584 là đủ để phân biệt các số từ các nước láng giềng. C, khi được yêu cầu in với độ chính xác 20 chữ số sau dấu thập phân chuyển giá trị được làm tròn chính xác thành số chữ số mong muốn, rõ ràng F # sử dụng biểu diễn xác định duy nhất ngắn nhất và đặt nó bằng số 0 mong muốn, giống như Haskell .

+0

Vì vậy, nếu tôi thực hiện nhiều thao tác trên danh sách những con số này, thì những khác biệt này có bắt đầu trở nên quan trọng không? Tôi đang cố gắng tìm ra cách tôi có thể kiểm tra xem đây có phải là trường hợp hay không ... – Benjol

+0

Điều đó tùy thuộc. Tôi nghĩ .NET cung cấp cho JITter khá nhiều thời gian, vì vậy kết quả sẽ không nhất thiết phải có thể lặp lại được trên các đoạn mã .NET khác nhau. Nếu việc triển khai C không yêu cầu tuân thủ IEC 60559, việc này cũng có nhiều thay đổi. Câu hỏi đặt ra là liệu C và .NET có tạo ra cùng một kết quả cho cùng một hàm với cùng các đầu vào hay không. Nếu có, bạn sẽ chỉ quan sát sự khác biệt của biểu diễn chuỗi, chỉ xuất hiện phía sau phần cần thiết để xác định giá trị 'double', không cái gì có thể tích lũy được. Nếu chúng không khác biệt 1 hoặc 2 ULP trong kết quả ... –

+0

có thể tích lũy. Nhưng trừ khi bạn thực hiện ** rất nhiều hoạt động (hoặc một số hoạt động kém), sự khác biệt sẽ vẫn còn nhỏ. –

3

Nó chỉ làm tròn khác nhau. Các con số giống nhau (theo CPython, ít nhất):

>>> '%.44f' % 71.94979858400000000000 
'71.94979858400000694018672220408916473388671875' 
>>> '%.44f' % 71.94979858400000700000 
'71.94979858400000694018672220408916473388671875' 
0

Câu trả lời khác giải thích đầy đủ nguồn gốc của vấn đề (độ chính xác kép và làm tròn).

Nếu số của bạn thường có độ lớn vừa phải và độ chính xác thập phân là rất quan trọng (moreso so với tốc độ tính toán) thì có thể xem xét sử dụng định dạng .NET decimal. Điều này mang lại cho bạn 28-29 vị trí thập phân chính xác chính xác mà không có các lỗi làm tròn nhị phân phân số như lỗi kép. Hạn chế là phạm vi nhỏ hơn (không có số mũ lớn!).

http://msdn.microsoft.com/en-us/library/364x0z75%28v=vs.100%29.aspx

3

Đây là NET System.Double.ToString() phương pháp đó là sự khác biệt, phương pháp có thể chuyển đổi một đôi thành một chuỗi. Bạn có thể xem mã có liên quan bằng cách tải xuống nguồn CLR như được cung cấp trong SSCLI20. Việc chuyển đổi được thực hiện bởi hàm clr/src/vm/comnumber.cpp, COMNumber :: FormatDouble(). Trong đó trông like this, nhận xét trong mã là mô tả nhất về những gì đang diễn ra:

//In order to give numbers that are both friendly to display and round-trippable, 
//we parse the number using 15 digits and then determine if it round trips to the same 
//value. If it does, we convert that NUMBER to a string, otherwise we reparse using 17 digits 
//and display that. 

Thư viện thời gian chạy C không có tính năng đó.

+0

Cảm ơn Hans, đó là câu trả lời khá thuyết phục. Tôi chắc chắn đang nhận được các lỗi tích lũy khi tôi thực hiện các thuật toán của mình, bây giờ tôi đã thử và theo dõi chính xác vị trí và lý do tại sao ... Hiện tại không thể tìm ra nếu nó là công cụ dấu chấm động hoặc các lỗi đơn giản trong mã. – Benjol

0

Thông tin thêm cho bất kỳ ai ngại về điều này.

Sử dụng bit mã được tìm thấy here, tôi đã xác nhận (tôi tin) xác nhận rằng biểu diễn nhị phân cơ bản (ít nhất là cho số cụ thể này) giống nhau.

Dưới đây là các mẫu mã - lưu ý 'nhân zero bằng 0' để loại bỏ số 0 âm - tức là xấu khi được chuyển đổi thành dài.

//(C# this time) 
var d = 71.9497985840; //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
var longval = System.BitConverter.DoubleToInt64Bits(d); // = 4634763433907061836 

Trong C:

double d; 
long long a; 
d = 71.9497985840;  //or other incoming double value 
if(d == 0) d = d * d; //for negative zero 
a = *(long long*)&d; //= 4634763433907061836 

cập nhật - Tôi đi theo qua, và phát hiện ra rằng sự khác biệt đã được giới thiệu trong ma trận đảo ngược, bởi vì mỗi hệ thống gọi ra một thư viện khác nhau, thực hiện đảo ngược trong một cách khác ...

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