2009-03-30 27 views
7

xem xét mã # C sau:Tại sao trật tự ảnh hưởng đến làm tròn khi thêm nhiều đôi trong C#

double result1 = 1.0 + 1.1 + 1.2; 
double result2 = 1.2 + 1.0 + 1.1; 

if (result1 == result2) 
{ 
    ... 
} 

result1 nên luôn bằng result2 phải không? Vấn đề là, nó không. result1 là 3,3 và result2 là 3.3000000000000003. Sự khác biệt duy nhất là thứ tự của các hằng số.

Tôi biết rằng việc tăng gấp đôi được triển khai theo cách mà các vấn đề làm tròn có thể xảy ra. Tôi biết rằng tôi có thể sử dụng số thập phân thay vì nếu tôi cần độ chính xác tuyệt đối. Hoặc tôi có thể sử dụng Math.Round() trong câu lệnh if của tôi. Tôi chỉ là một mọt sách, những người muốn hiểu những gì trình biên dịch C# đang làm. Bất cứ ai có thể cho tôi biết?

Edit:

Nhờ tất cả những người ấy cho đến nay đề nghị đọc lên trên nổi điểm số học và/hoặc nói về sự thiếu chính xác vốn có của cách CPU xử lý gấp đôi. Nhưng tôi cảm thấy lực đẩy chính của câu hỏi của tôi vẫn chưa được trả lời. Đó là lỗi của tôi vì không nói đúng cách. Hãy để tôi đặt nó như thế này:

Breaking xuống mã trên, tôi mong chờ các hoạt động sau đây để thể xảy ra:

double r1 = 1.1 + 1.2; 
double r2 = 1.0 + r1 
double r3 = 1.0 + 1.1 
double r4 = 1.2 + r3 

Giả sử rằng mỗi người trong số những bổ sung ở trên có làm tròn lỗi (đánh số e1. .e 4). Vì vậy, r1 chứa lỗi làm tròn e1, r2 bao gồm các lỗi làm tròn e1 + e2, r3 chứa e3 và r4 chứa e3 + e4.

Bây giờ, tôi không biết chính xác cách các lỗi làm tròn xảy ra nhưng tôi đã dự kiến ​​e1 + e2 bằng e3 + e4. Rõ ràng là không, nhưng điều đó có vẻ không đúng với tôi. Một điều nữa là khi tôi chạy mã trên, tôi không nhận được bất kỳ lỗi làm tròn nào. Đó là những gì làm cho tôi nghĩ rằng đó là trình biên dịch C# đó là làm một cái gì đó kỳ lạ chứ không phải là CPU.

Tôi biết tôi đang hỏi rất nhiều và có thể câu trả lời hay nhất mà mọi người có thể đưa ra là đi và làm PHD trong thiết kế CPU, nhưng tôi chỉ nghĩ rằng tôi sẽ hỏi.

Chỉnh sửa 2

Nhìn vào IL từ mẫu mã ban đầu của tôi, thì rõ ràng rằng đó là trình biên dịch không phải là CPU đó là làm điều này:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
.entrypoint 
    .maxstack 1 
    .locals init (
     [0] float64 result1, 
     [1] float64 result2) 
    L_0000: nop 
    L_0001: ldc.r8 3.3 
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003 
    L_0014: stloc.1 
    L_0015: ret 
} 

Trình biên dịch là thêm lên những con số cho tôi!

Trả lời

10

tôi dự kiến ​​sẽ có e1 + e2 đến e3 bình đẳng + e4.

Đó không phải là hoàn toàn không giống như mong đợi

floor(5/3) + floor(2/3 + 1) 

để bằng

floor(5/3 + 2/3) + floor(1) 

trừ bạn nhân với 2^53 trước khi sàn.

Sử dụng 12 bit chính xác điểm và cắt ngắn nổi với giá trị của bạn:

 
1.0   = 1.00000000000 
1.1   = 1.00011001100 
1.2   = 1.00110011001 

1.0 + 1.1  = 10.00011001100 // extended during sum 
r1 = 1.0 + 1.1 = 10.0001100110 // truncated to 12 bit 
r1 + 1.2  = 11.01001100101 // extended during sum 
r2 = r1 + 1.2 = 11.0100110010 // truncated to 12 bit 

1.1 + 1.2  = 10.01001100110 // extended during sum 
r3 = 1.1 + 1.2 = 10.0100110011 // truncated to 12 bit 
r3 + 1.0  = 11.01001100110 // extended during sum 
r4 = r3 + 1.0 = 11.0100110011 // truncated to 12 bit 

Vì vậy, thay đổi thứ tự của các hoạt động/sự cắt gây ra lỗi để thay đổi, và R4 = r2!. Nếu bạn thêm 1.1 và 1.2 trong hệ thống này, bit cuối cùng sẽ mang, do đó, không bị mất khi cắt. Nếu bạn thêm 1.0 đến 1.1, bit cuối cùng của 1.1 bị mất và do đó kết quả không giống nhau.

Trong một đơn đặt hàng, làm tròn (cắt ngắn) sẽ xóa một dấu sau 1.

Trong thứ tự khác, làm tròn sẽ xóa một đường nhỏ 0 cả hai lần.

Một không bằng 0; do đó các lỗi không giống nhau. Một đôi có nhiều bit chính xác hơn, và C# có thể sử dụng làm tròn thay vì cắt xén, nhưng hy vọng mô hình đơn giản này cho bạn thấy các lỗi khác nhau có thể xảy ra với các thứ tự khác nhau của cùng một giá trị.

Sự khác biệt giữa fp và toán học là + là viết tắt của 'thêm vòng tròn' thay vì chỉ thêm.

+0

+1 ví dụ cụ thể tốt – bobince

+0

Ví dụ là tốt, nhưng bạn đã thay đổi thứ tự của các hoạt động so với mã ban đầu. OP đã làm như vậy trong lần chỉnh sửa đầu tiên. –

2

Đặt hàng các hoạt động điểm nổi là quan trọng. Không trực tiếp trả lời câu hỏi của bạn, nhưng bạn nên luôn cẩn thận so sánh các số dấu phẩy động. Mọi người thường phải bao gồm sự khoan dung:

double epsilon = 0.0000001; 
if (abs(result1 - result2) <= epsilon) 
{ 
    ... 
} 

này có thể quan tâm: What Every Computer Scientist Should Know About Floating-Point Arithmetic

6

Các C# biên dịch không làm bất cứ điều gì. CPU là.

nếu bạn có một trong một thanh ghi CPU, và sau đó bạn thêm B, kết quả được lưu trữ trong sổ đăng ký đó là A + B, xấp xỉ với độ chính xác nổi sử dụng

Nếu bạn sau đó thêm C, sai số cho biết thêm lên . Lỗi bổ sung này không phải là một hoạt động chuyển tiếp, do đó sự khác biệt cuối cùng.

+0

Nhưng nếu A + B tạo lỗi làm tròn và A + C thì không, sẽ không (A + C) + B vẫn có lỗi làm tròn giống như A + B? Tôi nhận được rằng làm tròn lỗi thêm lên, tôi hỏi tại sao thứ tự quan trọng. – d4nt

+0

không, A + B tạo ra lỗi e1, và sau đó A + B + C tạo ra lỗi khác e2, do đó lỗi cuối cùng là e1 + e2. Nếu A + C không có lỗi, A + C + B cung cấp một lỗi duy nhất e3, không có lý do nào để khớp e1 + e2. Bạn nên google cho số học dấu chấm động để biết thêm thông tin. – Brann

+0

Tôi đoán tôi không nói rõ, hãy để tôi nói lại "nếu A + B tạo e1, (A + B) + C tạo e2, A + C tạo e3 và (A + C) + B tạo e4, tại sao e1 + e2 không bằng e3 + e4? Cho đến nay, bạn đã nói với tôi rằng nó không nhưng không phải lý do tại sao nó không. " – d4nt

0

Bạn đang thực sự không sử dụng cùng một giá trị vì các kết quả trung gian khác nhau:

double result1 = 2.1 + 1.2; 
double result2 = 2.2 + 1.1; 

Bởi vì đôi không thể đại diện giá trị thập phân chính xác bạn sẽ có được kết quả khác nhau.

4

Xem the classic paper (What every computer scientist should know about floating-point arithmetic) về chủ đề. Loại công cụ này là những gì xảy ra với số học dấu chấm động. Phải mất một nhà khoa học máy tính để cho bạn biết rằng 1/3 + 1/3 + 1/3 is'nt bằng 1 ...

+0

Tôi thừa nhận rằng số học dấu chấm động làm tôi sợ. Theo quy tắc chung, tôi từ chối sử dụng nó trừ khi tôi phải làm vậy. –

+0

Sử dụng chúng khi hữu ích: trong đó xấp xỉ là OK. Mô phỏng vật lý, các phép đo thu được từ bên ngoài, bố cục đồ họa (cho các mức độ gần đúng khác nhau của phép xấp xỉ). Tiền - không quá nhiều! Không có công cụ đơn giản đến nỗi bạn không thể làm tổn thương bản thân với nó ... (hm, tai nạn nhút nhát chết người?). –

1

Tại sao các lỗi không giống nhau tùy thuộc vào thứ tự có thể được giải thích bằng một ví dụ khác.Giả sử đối với các số dưới 10, nó có thể lưu trữ tất cả các số, vì vậy nó có thể lưu trữ 1, 2, 3, v.v. lên đến 10, nhưng sau 10, nó chỉ có thể lưu trữ mọi số thứ hai, do mất nội bộ chính xác, nói cách khác, nó có thể chỉ lưu trữ 10, 12, 14 vv

Bây giờ, với ví dụ đó, bạn sẽ thấy tại sao những điều sau đây tạo ra kết quả khác nhau:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding) 
10 + 1 + 1 + 1 = 10 

Vấn đề với số dấu chấm động là chúng không thể được biểu diễn chính xác và lỗi không phải lúc nào cũng giống như vậy, do đó thứ tự sẽ quan trọng.

Ví dụ, 3,00000000003 + 3,00000000003 có thể kết thúc là 6,00000000005 (thông báo không 6 ở cuối), nhưng 3,00000000003 + 2,99999999997 có thể kết thúc là 6,00000000001, và với điều đó:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005 
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002 

nhưng, thay đổi thứ tự :

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001 
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004 

Vì vậy, nó sẽ quan trọng.

Bây giờ, tất nhiên, bạn có thể may mắn khi các ví dụ trên cân bằng lẫn nhau, trong đó các ví dụ đầu tiên sẽ tăng lên .xxx1 và cái còn lại bằng .xxx1, cho bạn .xxx3 ở cả hai, nhưng không bảo đảm.

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