2013-08-01 50 views
34

Tôi nhận thấy một hành vi lạ khi nhân các giá trị thập phân trong C#. Hãy xem xét các phép toán nhân sau đây:C# số thập phân hành vi lạ

1.1111111111111111111111111111m * 1m = 1.1111111111111111111111111111 // OK 
1.1111111111111111111111111111m * 2m = 2.2222222222222222222222222222 // OK 
1.1111111111111111111111111111m * 3m = 3.3333333333333333333333333333 // OK 
1.1111111111111111111111111111m * 4m = 4.4444444444444444444444444444 // OK 
1.1111111111111111111111111111m * 5m = 5.5555555555555555555555555555 // OK 
1.1111111111111111111111111111m * 6m = 6.6666666666666666666666666666 // OK 
1.1111111111111111111111111111m * 7m = 7.7777777777777777777777777777 // OK 
1.1111111111111111111111111111m * 8m = 8.888888888888888888888888889 // Why not 8.8888888888888888888888888888 ? 
1.1111111111111111111111111111m * 9m = 10.000000000000000000000000000 // Why not 9.9999999999999999999999999999 ? 

Điều tôi không thể hiểu là hai trường hợp trên cùng. Làm thế nào là có thể?

+11

Chào mừng bạn đến với thế giới tuyệt vời của lỗi chính xác. – christopher

+0

Đây không phải là lỗi chính xác, nó chỉ làm tròn. –

+0

Đây là một câu đố toán học cho bạn: '10/9 = 1.111…'; '1.111… * 9 = 9,999… ≠ 10'. ;) – stakx

Trả lời

73

decimal lưu trữ 28 hoặc 29 chữ số có nghĩa (96 bit). Về cơ bản, phần định trị nằm trong phạm vi -/79,228,162,514,264,337,593,543,950,335.

Điều đó có nghĩa là tối đa khoảng 7,9 .... bạn có thể nhận được 29 chữ số có nghĩa chính xác - nhưng ở trên mà bạn không thể. Đó là lý do tại sao cả 8 và 9 đều sai, nhưng không phải là giá trị trước đó. Bạn chỉ nên dựa trên 28 chữ số có nghĩa nói chung, để tránh các tình huống kỳ lạ như thế này.

Khi bạn giảm đầu vào ban đầu của bạn lên 28 con số đáng kể, bạn sẽ nhận được kết quả bạn mong đợi:

using System; 

class Test 
{ 
    static void Main() 
    { 
     var input = 1.111111111111111111111111111m; 
     for (int i = 1; i < 10; i++) 
     { 
      decimal output = input * (decimal) i; 
      Console.WriteLine(output); 
     } 
    } 
} 
+0

Tôi không chắc chính xác ý bạn là gì khi "dựa vào" 28 chữ số. Bạn có nghĩa là làm tròn rõ ràng những thứ như kết quả của các bộ phận? Thực tế là 'Decimal' sử dụng số mũ cơ số mười thay vì base-two có nghĩa là giá trị số danh nghĩa của một' Decimal' sẽ khớp với giá trị của biểu diễn chuỗi ngắn gọn của nó, nhưng thực tế là nó là loại dấu phẩy động -testing với 'Decimal' là có cùng các vấn đề như với bất kỳ loại dấu phẩy động nào khác. – supercat

+0

@supercat: Ý tôi là nếu bạn có 29 chữ số bạn cần đại diện, bạn có thể không thực hiện được chính xác, nhưng bạn có thể biểu diễn tối đa 28 chữ số chính xác. Tôi nghĩ sẽ hợp lý hơn khi dựa vào sự bình đẳng cho 'thập phân' hơn là' float'/'double' - cho một điều, bạn không cần phải lo lắng về khả năng một số hoạt động được thực hiện với độ chính xác cao hơn tùy thuộc vào việc giá trị là trong một đăng ký hay không.Nó sẽ vẫn phải được thực hiện với sự chăm sóc đáng kể, nhưng trong nhiều trường hợp tôi nghĩ rằng nó sẽ là tốt. –

2

nhà toán học phân biệt giữa số hữu tỉ và superset số thực. Các phép tính số học trên các số hữu tỷ được xác định rõ và chính xác. Số học (sử dụng toán tử cộng, trừ, nhân và chia) trên số thực là "chính xác" trong phạm vi số không hợp lý được để lại dưới dạng không hợp lý (biểu tượng) hoặc có thể chuyển đổi trong một số biểu thức thành số hữu tỉ . Ví dụ, căn bậc hai của hai không có biểu diễn thập phân (hoặc bất kỳ cơ sở hợp lý nào khác). Tuy nhiên, căn bậc hai của hai nhân với căn bậc hai của hai là hợp lý - 2, rõ ràng.

Máy tính và các ngôn ngữ chạy trên chúng thường chỉ thực hiện các số hợp lý - ẩn sau các tên như int, long int, float, độ chính xác kép, thực (FORTRAN) hoặc một số tên khác. Nhưng những con số hợp lý bao gồm bị giới hạn, không giống như tập hợp các số hữu tỉ có phạm vi vô hạn.

Ví dụ nhỏ - không tìm thấy trên máy tính. 1/2 * 1/2 = 1/4 Điều đó hoạt động tốt nếu bạn có một lớp số Rational và kích thước của tử số và mẫu số không vượt quá giới hạn số học số nguyên. vì vậy (1,2) * (1,2) -> (1,4)

Nhưng nếu số hợp lý có sẵn là số thập phân VÀ giới hạn ở một chữ số sau số thập phân - không thực tế - nhưng đại diện của lựa chọn được thực hiện khi chọn một thực hiện cho xấp xỉ các số hợp lý (float/real, etc.), sau đó 1/2 sẽ được chuyển đổi hoàn toàn thành 0.5, sau đó 0.5 + 0.5 sẽ bằng 1.0, nhưng 0.5 * 0.5 sẽ là 0.2 hoặc 0.3!