2009-02-19 8 views
15

Tôi có một đơn giản C# chức năng:vấn đề chính xác đúp vào NET

public static double Floor(double value, double step) 
{ 
    return Math.Floor(value/step) * step; 
} 

Đó sẽ tính toán số lượng cao hơn, thấp hơn hoặc bằng "giá trị", đó là bội số của "bước". Nhưng nó thiếu chính xác, như đã thấy trong các bài kiểm tra sau:

[TestMethod()] 
public void FloorTest() 
{ 
    int decimals = 6; 
    double value = 5F; 
    double step = 2F; 
    double expected = 4F; 
    double actual = Class.Floor(value, step); 
    Assert.AreEqual(expected, actual); 
    value = -11.5F; 
    step = 1.1F; 
    expected = -12.1F; 
    actual = Class.Floor(value, step); 
    Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals)); 
    Assert.AreEqual(expected, actual); 
} 

Việc đầu tiên và thứ hai khẳng định là ok, nhưng thứ ba thất bại, bởi vì kết quả là chỉ tương đương cho đến khi nơi thập phân thứ 6. Tại sao vậy? Có cách nào để khắc phục điều này?

Cập nhật Nếu tôi gỡ lỗi thử nghiệm, tôi thấy rằng các giá trị bằng nhau cho đến vị trí thập phân thứ 8 thay vì thứ 6, có thể do Math.Round giới thiệu một số sự không chính xác.

Lưu ý Trong mã thử nghiệm, tôi đã viết hậu tố "F" (hằng số nổi rõ ràng), nghĩa là "D" (gấp đôi), vì vậy nếu tôi thay đổi.

Trả lời

5

Nếu bạn bỏ qua tất cả các hậu tố F (tức là -12.1 thay vì -12.1F), bạn sẽ nhận được sự bình đẳng với một vài chữ số. Hằng số của bạn (và đặc biệt là các giá trị mong đợi) hiện đang nổi vì F. Nếu bạn đang làm điều đó có mục đích thì hãy giải thích.

Nhưng đối với phần còn lại, tôi đồng ý với các câu trả lời khác về so sánh các giá trị kép hoặc float cho bình đẳng, nó không đáng tin cậy.

+0

nhưng chữ hoa F có nghĩa là gấp đôi, không nổi, phải không? là chữ thường f có nghĩa là phao. –

+0

Không, tôi vừa kiểm tra: float x = 1.0; đưa ra một lỗi, float x = 1.0F; OK. F không phân biệt chữ hoa chữ thường. –

+1

Và nhìn nó trong Ecmea334: 1.0D cho đôi, 1.0M cho thập phân. –

8

Floating điểm số học trên các máy tính được không chính xác khoa học :).

Nếu bạn muốn độ chính xác chính xác với số thập phân được xác định trước, hãy sử dụng thập phân thay vì tăng gấp đôi hoặc chấp nhận một khoảng nhỏ.

+5

Đây là một khoa học chính xác trong số các chữ số có nghĩa được xác định bởi IEEE. –

+1

Để củng cố ở trên: số dấu phẩy động _are_ chính xác. Số bạn muốn có thể không thể được biểu diễn dưới dạng số dấu phẩy động IEEE, có nghĩa là bạn phải bí danh số này đến số gần nhất, dẫn đến lỗi, nhưng điều đó không có nghĩa là số mà bạn _can_ đại diện có lỗi chúng. – codekaizen

+2

Ngoài ra, số thập phân có thể gặp vấn đề tương tự như đôi, vì chúng cũng là điểm nổi. Họ có thể gặp phải vấn đề đại diện tương tự, nhưng nó ít bất ngờ hơn, vì họ có cơ sở là 10, so với căn cứ 2, và chúng ta được sử dụng để xử lý các vấn đề biểu diễn trong cơ sở 10 (ví dụ 1/3 là 0.333333 .. trong cơ sở 10). – codekaizen

1

phao và đôi không thể lưu trữ chính xác tất cả các số. Đây là một hạn chế với hệ thống điểm nổi IEEE. Để có độ chính xác trung thành, bạn cần sử dụng thư viện toán học nâng cao hơn.

Nếu bạn không cần độ chính xác qua một điểm nhất định, thì có lẽ thập phân sẽ hoạt động tốt hơn cho bạn. Nó có độ chính xác cao hơn gấp đôi.

4

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Ví dụ, phi representability là 0,1 và 0,01 (ở dạng nhị phân) có nghĩa là kết quả của sự cố gắng vuông 0.1 không phải là 0.01 cũng không phải là số biểu diễn gần gũi nhất với nó.

Chỉ sử dụng dấu phẩy động nếu bạn muốn giải thích của máy (nhị phân) của các hệ thống số. Bạn không thể đại diện cho 10 xu.

4

Nếu bạn muốn chính xác, hãy sử dụng System.Decimal. Nếu bạn muốn tốc độ, hãy sử dụng System.Double (hoặc System.Float). Số dấu chấm động không phải là số "chính xác vô hạn", và do đó khẳng định sự bình đẳng phải bao gồm một sự khoan dung. Miễn là số của bạn có số chữ số có ý nghĩa hợp lý, điều này là ok.

  • Nếu bạn đang tìm cách thực hiện toán học với số lượng rất lớn và rất nhỏ, không sử dụng float hoặc double.
  • Nếu bạn cần độ chính xác vô hạn, không sử dụng phao hoặc kép.
  • Nếu bạn đang tập hợp một số lượng lớn các giá trị, không sử dụng float hoặc double (các lỗi sẽ tự kết hợp).
  • Nếu bạn cần tốc độ và kích thước, hãy sử dụng phao hoặc kép.

Xem this câu trả lời (cũng bởi tôi) để biết phân tích chi tiết về độ chính xác ảnh hưởng đến kết quả hoạt động toán học của bạn như thế nào.

+0

Không có 'độ chính xác vô hạn'. Vấn đề với float/double là chúng chính xác với một số chữ số nhị phân và không phải là một số chữ số thập phân. – configurator

+2

Có một thứ như độ chính xác vô hạn.Một số nguyên là một loại vô cùng chính xác. Nó không có độ chính xác trong các phép toán. Có thể thực hiện một loại thập phân vô cùng chính xác (mặc dù rất không hiệu quả), nhưng nó không phải là "ngoài hộp" trong .Net. –

+0

Michael, bình luận của bạn là ngớ ngẩn. Được định nghĩa theo cách này, để các số nguyên có thể được gọi là "vô hạn precison" chỉ vì chúng không thay đổi trong một phép toán, sau đó mọi số đều có "Độ chính xác vô hạn", (phao, số đôi và số thập phân không thay đổi trong khi chọn toán học) . Được định nghĩa theo cách này, khái niệm mất tất cả ý nghĩa. Heck, thậm chí chỉ là một dấu hiệu chỉ có hai giá trị, (Tích cực hoặc Phủ định) có "Độ chính xác vô hạn", bởi vì, theo định nghĩa của bạn, nó không mất chính xác trong bất kỳ hoạt động toán học nào được sử dụng. –

0

Đối với các vấn đề tương tự, tôi sẽ chỉ sử dụng thực hiện sau đây mà dường như thành công nhất của trường hợp thử nghiệm của tôi (lên đến độ chính xác 5 chữ số):

public static double roundValue(double rawValue, double valueTick) 
{ 
    if (valueTick <= 0.0) return 0.0; 

    Decimal val = new Decimal(rawValue); 
    Decimal step = new Decimal(valueTick); 
    Decimal modulo = Decimal.Round(Decimal.Divide(val,step)); 

    return Decimal.ToDouble(Decimal.Multiply(modulo, step)); 
} 
8

Tôi thực sự muốn loại họ đã không được thực hiện toán tử == cho float và double. Nó gần như luôn luôn là điều sai trái để làm bao giờ hỏi nếu một đôi hoặc một phao bằng bất kỳ giá trị nào khác.

+1

CÓ! VÂNG! VÂNG! Tôi đã nói điều đó một lúc rồi. Nó giống như toàn bộ 0.999 ... = 1.0 vấn đề. (1.0 - 0.000 ... = 1.0). Điểm nổi là một động vật hoàn toàn khác với số nguyên. – Josh

0

Đôi khi kết quả chính xác hơn bạn mong đợi từ nghiêm ngặt: FP IEEE 754. Đó là vì HW sử dụng nhiều bit hơn cho tính toán. Xem C# specificationthis article

Java có từ khóa strictfp và C++ có trình chuyển đổi trình biên dịch. Tôi nhớ tùy chọn đó trong .NET