2010-07-14 48 views
7

Tôi vô cùng khó chịu bởi sự thiếu chính xác của các hàm trig nội tại trong CLR. Nó cũng biết rằngĐộ chính xác của Math.Sin() và Math.Cos() trong C#

Math.Sin(Math.PI)=0.00000000000000012246063538223773 

thay vì 0. Điều gì đó tương tự xảy ra với Math.Cos(Math.PI/2).

Nhưng khi tôi đang làm một chuỗi dài các tính toán rằng trên trường hợp đặc biệt đánh giá để

Math.Sin(Math.PI/2+x)-Math.Cos(x) 

và kết quả là zero cho x = 0,2, nhưng không phải không cho x = 0,1 (thử nó). Một vấn đề khác là khi đối số là một số lượng lớn, sự thiếu chính xác sẽ tương ứng lớn.

Vì vậy, tôi tự hỏi nếu có ai đã mã hóa một số đại diện tốt hơn về các chức năng trig trong C# để chia sẻ với thế giới. CLR có gọi một số thư viện toán C chuẩn thực hiện CORDIC hay một cái gì đó tương tự? liên kết: wikipedia CORDIC

+8

Bạn cho rằng độ chính xác của pi là gấp đôi là bao nhiêu? –

+3

Nếu bạn muốn toán học biểu tượng, hãy làm toán học biểu tượng. Nếu bạn sử dụng các loại dấu phẩy động, bạn sẽ có được độ chính xác hữu hạn. – AakashM

+4

-1 vì không "làm bài tập ở nhà", và cũng nghĩ rằng 'System.Math' là một phần của C# (gợi ý: nó là một phần của.Nền tảng NET). –

Trả lời

2

Bạn cần sử dụng thư viện thập phân chính xác tùy ý. (.Net 4.0 có arbitrary integer class, nhưng không phải là số thập phân).

Một vài những người nổi tiếng có sẵn:

18

Điều này không liên quan gì đến tính chính xác của hàm lượng giác nhưng nhiều hơn với hệ thống kiểu CLS. Theo tài liệu, double có độ chính xác 15-16 chữ số (chính xác là những gì bạn nhận được) để bạn không thể chính xác hơn với loại này. Vì vậy, nếu bạn muốn chính xác hơn, bạn sẽ cần phải tạo một loại mới có khả năng lưu trữ nó.

Cũng cần chú ý rằng bạn không bao giờ nên được viết một mã như thế này:

double d = CalcFromSomewhere(); 
if (d == 0) 
{ 
    DoSomething(); 
} 

Bạn nên làm thay vì:

double d = CalcFromSomewhere(); 
double epsilon = 1e-5; // define the precision you are working with 
if (Math.Abs(d) < epsilon) 
{ 
    DoSomething(); 
} 
6

Đây là một kết quả của dấu chấm động chính xác. Bạn nhận được một số chữ số có nghĩa nhất định có thể và bất kỳ thứ gì không thể được biểu diễn chính xác là xấp xỉ. Ví dụ, pi không phải là một số hợp lý, và vì vậy nó không thể có được một đại diện chính xác. Vì bạn không thể có được một giá trị chính xác của pi, bạn sẽ không nhận được chính xác sin và cosines của các số bao gồm pi (cũng như bạn sẽ không nhận được giá trị chính xác của sin và cosin phần lớn thời gian).

Giải thích trung gian tốt nhất là "What Every Computer Scientist Should Know About Floating-Point Arithmetic". Nếu bạn không muốn đi vào đó, chỉ cần nhớ rằng các số dấu chấm động thường xấp xỉ, và các tính toán dấu phẩy động giống như di chuyển đống cát trên mặt đất: với mọi thứ bạn làm với chúng, bạn mất một ít cát và nhặt một ít bụi bẩn.

Nếu bạn muốn đại diện chính xác, bạn sẽ cần phải tìm cho mình một hệ thống đại số tượng trưng.

+0

Tôi hiểu nếu PI không được xác định chính xác do số học IEEE-754, và tôi ước tôi có thể lái một hệ thống đại số tượng trưng với C#, nhưng tôi không thể ngay bây giờ. – ja72

9

Tôi nghe bạn. Tôi vô cùng khó chịu bởi sự không chính xác của bộ phận. Một ngày khác tôi đã làm:

Console.WriteLine(1.0/3.0); 

và tôi nhận được 0,333333333333333, thay vì trả lời đúng là 0.3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333 ...

Có lẽ bây giờ bạn thấy vấn đề là gì. Math.Pi không bằng pi bất kỳ hơn 1.0/3.0 bằng 1/3. Cả hai đều khác với giá trị thực của vài trăm phần nghìn, và do đó bất kỳ phép tính nào bạn thực hiện với Math.Pi hoặc 1.0/3.0 cũng sẽ bị giảm đi vài trăm phần nghìn, bao gồm cả việc lấy sin.

Nếu bạn không thích số học gần đúng đó là gần đúng thì không sử dụng số học gần đúng. Sử dụng số học chính xác. Tôi từng sử dụng Waterloo Maple khi tôi cần số học chính xác; có lẽ bạn nên mua một bản sao đó.

+0

Hết sức tò mò, nếu muốn tính toán số học chính xác trong C#, liệu nó có khả thi không? Chỉ cần không biết những gì người ta có thể sử dụng để làm điều đó? Thậm chí không phải số thập phân sẽ cắt nó, phải không? –

+2

@Joan: Có, chúng ta có thể làm điều đó dễ dàng cho các số nguyên, và thậm chí cho số hữu tỉ (chỉ cần lưu trữ tử số/mẫu số là số nguyên lớn) và ném các quy tắc vào thư viện của chúng tôi cho bất kỳ số thực cụ thể nào chúng ta muốn: pi, e, square- rễ, vv Tuy nhiên, một thư viện cho số học chính xác tùy ý trên * bất kỳ * số thực có thể tưởng tượng là không thể; thậm chí giả sử bạn có một số cách để lưu trữ chúng * (nói như một công thức sẽ cho chúng ta một số tùy ý của số) *, nó là không thể tính toán để so sánh hai số thực bất kỳ cho sự bình đẳng !! –

+1

@BlueRaja: thực sự. Thông thường những gì bạn làm trong trường hợp đó là thao tác đại lượng số học theo biểu tượng; bạn có biểu tượng cho pi và ký hiệu cho e, giống như cách bạn có biểu tượng cho 1, 2, 3 và sau đó bạn mã hóa tất cả số nhận dạng số học và lượng giác, như sin của pi bằng 0, v.v. Toán học tượng trưng là khó. –

1

tôi từ chối ý tưởng của các lỗi này là do vòng-off. Có thể làm gì là xác định sin(x) như sau, sử dụng mở rộng của Taylor với 6 học kỳ:

const double π=Math.PI; 
    const double π2=Math.PI/2; 
    const double π4=Math.PI/4; 

    public static double Sin(double x) 
    { 

     if (x==0) { return 0; } 
     if (x<0) { return -Sin(-x); } 
     if (x>π) { return -Sin(x-π); } 
     if (x>π4) { return Cos(π2-x); } 

     double x2=x*x; 

     return x*(x2/6*(x2/20*(x2/42*(x2/72*(x2/110*(x2/156-1)+1)-1)+1)-1)+1); 
    } 

    public static double Cos(double x) 
    { 
     if (x==0) { return 1; } 
     if (x<0) { return Cos(-x); } 
     if (x>π) { return -Cos(x-π); } 
     if (x>π4) { return Sin(π2-x); } 

     double x2=x*x; 

     return x2/2*(x2/12*(x2/30*(x2/56*(x2/90*(x2/132-1)+1)-1)+1)-1)+1; 
    } 

lỗi điển hình là 1e-16 và trường hợp xấu nhất là 1e-11. Nó tồi tệ hơn CLR, nhưng nó có thể điều khiển được bằng cách thêm nhiều thuật ngữ hơn. Tin tốt là đối với các trường hợp đặc biệt trong OP và cho Sin(45°) câu trả lời là chính xác.

+0

Bài đăng liên quan về góc độ nào có chính xác trig. Các giá trị http://math.stackexchange.com/q/176889/3301 – ja72

+0

Nếu vấn đề duy nhất là các trường hợp đặc biệt của OP, người ta cũng có thể viết chúng ra như là các câu lệnh if-else thay vì đi với một giải pháp như vậy. Các phương pháp này có thể hoạt động khi được gọi trực tiếp với ví dụ: không, nhưng không phải là giá trị gần đúng. Nếu ví dụ: bạn gọi Sin với kết quả gần đúng của một thao tác trước đó, nó sẽ không hoạt động (ví dụ vì x rất nhỏ, nhưng không chính xác bằng 0 - ngay cả khi nó "nên"). Vấn đề thực sự ở đây là việc sử dụng gấp đôi và mong đợi để có được kết quả chính xác, mà không có thuật toán hay công thức nào trên thế giới có thể khắc phục được. – enzi

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