Làm thế nào để C tính sin () và các hàm toán học khác?


248

Tôi đã xem qua các phân tách .NET và mã nguồn GCC, nhưng dường như không thể tìm thấy bất cứ nơi nào thực hiện thực tế sin()và các hàm toán học khác ... chúng dường như luôn tham chiếu một cái gì đó khác.

Bất cứ ai có thể giúp tôi tìm thấy chúng? Tôi cảm thấy không chắc là TẤT CẢ phần cứng mà C sẽ chạy trên đều hỗ trợ các chức năng trig trong phần cứng, vì vậy phải có thuật toán phần mềm ở đâu đó , phải không?


Tôi biết một số cách mà các hàm có thể được tính toán và đã viết các thói quen của riêng tôi để tính toán các hàm bằng cách sử dụng chuỗi taylor cho vui. Tôi tò mò về cách thực tế, các ngôn ngữ sản xuất làm điều đó, vì tất cả các triển khai của tôi luôn chậm hơn vài bậc, mặc dù tôi nghĩ thuật toán của tôi khá thông minh (rõ ràng là không phải vậy).


2
Xin lưu ý rằng việc thực hiện này phụ thuộc. Bạn nên chỉ định triển khai nào bạn quan tâm nhất.
jason

3
Tôi đã gắn thẻ .NET và C vì tôi đã tìm kiếm ở cả hai nơi và không thể tìm ra. Mặc dù nhìn vào quá trình tháo gỡ .NET, có vẻ như nó có thể được gọi vào C không được quản lý, theo như tôi biết họ có cùng cách thực hiện.
Hank

Câu trả lời:


213

Trong GNU libm, việc thực hiện sinphụ thuộc vào hệ thống. Do đó, bạn có thể tìm thấy việc triển khai, cho mỗi nền tảng, ở đâu đó trong thư mục con thích hợp của sysdeps .

Một thư mục bao gồm triển khai bằng C, được đóng góp bởi IBM. Kể từ tháng 10 năm 2011, đây là mã thực sự chạy khi bạn gọi sin()trên hệ thống Linux x86-64 điển hình. Nó rõ ràng là nhanh hơn so với fsinhướng dẫn lắp ráp. Mã nguồn: sysdeps / ieee754 / dbl-64 / s_sin.c , tìm kiếm __sin (double x).

Mã này rất phức tạp. Không một thuật toán phần mềm nào nhanh nhất có thể và cũng chính xác trong toàn bộ phạm vi của các giá trị x , vì vậy thư viện thực hiện một số thuật toán khác nhau và công việc đầu tiên của nó là xem xét x và quyết định sử dụng thuật toán nào.

  • Khi x là rất rất gần 0, sin(x) == xlà câu trả lời đúng.

  • Xa hơn một chút, sin(x)sử dụng loạt Taylor quen thuộc. Tuy nhiên, điều này chỉ chính xác gần 0, vì vậy ...

  • Khi góc lớn hơn khoảng 7 °, một thuật toán khác được sử dụng, tính toán các xấp xỉ chuỗi Taylor cho cả sin (x) và cos (x), sau đó sử dụng các giá trị từ bảng được tính toán trước để tinh chỉnh xấp xỉ.

  • Khi nào | x | > 2, không có thuật toán nào ở trên hoạt động, vì vậy mã bắt đầu bằng cách tính một số giá trị gần bằng 0 có thể được đưa vào sinhoặc costhay vào đó.

  • Vẫn còn một nhánh khác để đối phó với x là NaN hoặc vô cực.

Mã này sử dụng một số hack mà tôi chưa từng thấy trước đây, mặc dù đối với tất cả tôi biết chúng có thể nổi tiếng trong số các chuyên gia về dấu phẩy động. Đôi khi một vài dòng mã sẽ mất vài đoạn để giải thích. Ví dụ, hai dòng này

double t = (x * hpinv + toint);
double xn = t - toint;

được sử dụng (đôi khi) trong việc giảm x xuống một giá trị gần bằng 0 khác với x bởi bội số của π / 2, cụ thể là xn× π / 2. Cách thức này được thực hiện mà không phân chia hoặc phân nhánh là khá thông minh. Nhưng không có bình luận nào cả!


Các phiên bản 32-bit cũ hơn của GCC / glibc đã sử dụng fsinhướng dẫn, điều này không chính xác một cách đáng ngạc nhiên đối với một số đầu vào. Có một bài đăng blog hấp dẫn minh họa điều này chỉ với 2 dòng mã .

Việc thực hiện fdlibm sintrong thuần C đơn giản hơn nhiều so với glibc và được nhận xét độc đáo. Mã nguồn: fdlibm / s_sin.cfdlibm / k_sin.c


35
Để thấy rằng đây thực sự là mã chạy trên x86: biên dịch một chương trình gọi sin(); loại gdb a.out, sau đó break sin, sau đó run, sau đó disassemble.
Jason Orendorff

5
@Henry: đừng phạm sai lầm khi nghĩ rằng đó là mã tốt. Điều đó thực sự khủng khiếp , đừng học cách viết mã theo cách đó!
Thomas Bonini

2
@Andreas Hmm, bạn nói đúng, mã IBM trông khá tệ so với fdlibm. Tôi đã chỉnh sửa câu trả lời để thêm các liên kết đến thói quen sin của fdlibm.
Jason Orendorff

3
@Henry: __kernel_sinđược định nghĩa trong k_sin.c, tuy nhiên, nó hoàn toàn C. Nhấp vào nó một lần nữa. Tôi đã tìm kiếm URL lần đầu tiên.
Jason Orendorff

3
Mã sysdeps được liên kết đặc biệt thú vị vì nó được làm tròn chính xác. Đó là, nó rõ ràng đưa ra câu trả lời tốt nhất có thể cho tất cả các giá trị đầu vào, điều này chỉ trở nên khả thi gần đây. Trong một số trường hợp, điều này có thể chậm vì nhiều chữ số phụ có thể cần được tính toán để đảm bảo làm tròn chính xác. Trong các trường hợp khác, nó cực kỳ nhanh chóng - đối với các số đủ nhỏ, câu trả lời chỉ là góc.
Bruce Dawson

66

Các chức năng như sin và cosine được thực hiện trong microcode bên trong bộ vi xử lý. Chip Intel, ví dụ, có hướng dẫn lắp ráp cho những thứ này. Trình biên dịch AC sẽ tạo mã gọi các hướng dẫn lắp ráp này. (Ngược lại, trình biên dịch Java sẽ không. Java đánh giá các hàm trig trong phần mềm thay vì phần cứng và do đó, nó chạy chậm hơn nhiều.)

Chips không sử dụng chuỗi Taylor để tính toán các hàm lượng giác, ít nhất là không hoàn toàn. Trước hết họ sử dụng CORDIC , nhưng họ cũng có thể sử dụng một chuỗi Taylor ngắn để đánh bóng kết quả của CORDIC hoặc cho các trường hợp đặc biệt như tính toán sin với độ chính xác tương đối cao cho các góc rất nhỏ. Để giải thích thêm, hãy xem câu trả lời StackOverflow này .


10
Các hàm toán học siêu việt như sin & cosine có thể được triển khai bằng microcode hoặc như các hướng dẫn phần cứng trong bộ xử lý máy tính để bàn và máy chủ 32 bit hiện tại. Điều này không phải lúc nào cũng đúng, cho đến khi i486 (DX), tất cả các phép tính dấu phẩy động được thực hiện trong phần mềm ("phao mềm") cho sê-ri x86 mà không có bộ xử lý riêng. Không phải tất cả trong số đó (FPU) bao gồm các chức năng siêu việt (ví dụ Weitek 3167).
mctylr

1
Bạn có thể cụ thể hơn không? Làm thế nào để một người "đánh bóng" một xấp xỉ bằng cách sử dụng một loạt Taylor?
Hank

4
Theo như "đánh bóng" một câu trả lời, giả sử bạn đang tính cả sin và cos. Giả sử bạn biết giá trị chính xác của cả hai tại một điểm (ví dụ từ CORDIC) nhưng muốn giá trị ở điểm gần đó. Sau đó, với một sự khác biệt nhỏ h, bạn có thể áp dụng các xấp xỉ Taylor f (x + h) = f (x) + h f '(x) hoặc f (x + h) = f (x) + h f' (x) + h ^ 2 f '' (x) / 2.
John D. Cook

6
Chip x86 / x64 có hướng dẫn lắp ráp để tính sin (fsin) nhưng hướng dẫn này đôi khi không chính xác và do đó hiếm khi được sử dụng nữa. Xem Randomascii.wordpress.com/2014/10/09/ Khăn để biết chi tiết. Hầu hết các bộ xử lý khác không có hướng dẫn cho sin và cosin vì tính toán chúng trong phần mềm mang lại sự linh hoạt hơn, và thậm chí có thể nhanh hơn.
Bruce Dawson

3
Các công cụ thân mật bên trong chip intel thường KHÔNG được sử dụng. Đầu tiên, độ chính xác và độ phân giải của hoạt động là cực kỳ quan trọng đối với nhiều ứng dụng. Cordic nổi tiếng là không chính xác khi bạn đạt đến chữ số thứ 7 hoặc lâu hơn, và không thể đoán trước. Thứ hai, tôi nghe nói có một lỗi trong quá trình thực hiện của họ, điều này gây ra nhiều vấn đề hơn nữa. Tôi đã xem xét chức năng sin cho linux gcc, và chắc chắn, nó sử dụng ch Quashev. các công cụ tích hợp không được sử dụng. Ồ, ngoài ra, thuật toán thân mật trong chip chậm hơn giải pháp phần mềm.
Donald Murray

63

OK kiddies, thời gian cho các ưu điểm .... Đây là một trong những phàn nàn lớn nhất của tôi với các kỹ sư phần mềm thiếu kinh nghiệm. Họ tính toán các chức năng siêu việt từ đầu (sử dụng chuỗi của Taylor) như thể chưa từng có ai thực hiện các tính toán này trước đây trong cuộc sống của họ. Không đúng. Đây là một vấn đề được xác định rõ và đã được tiếp cận hàng ngàn lần bởi các kỹ sư phần mềm và phần cứng rất thông minh và có một giải pháp được xác định rõ. Về cơ bản, hầu hết các hàm siêu việt đều sử dụng Đa thức Ch Quashev để tính toán chúng. Các đa thức được sử dụng phụ thuộc vào hoàn cảnh. Đầu tiên, cuốn kinh thánh về vấn đề này là một cuốn sách có tên "Các xấp xỉ máy tính" của Hart và Cheney. Trong cuốn sách đó, bạn có thể quyết định xem bạn có bộ cộng phần cứng, số nhân, bộ chia, v.v. và quyết định thao tác nào là nhanh nhất. ví dụ: nếu bạn có một dải phân cách thực sự nhanh, cách nhanh nhất để tính sin có thể là P1 (x) / P2 (x) trong đó P1, P2 là đa thức Ch Quashev. Nếu không có bộ chia nhanh, nó có thể chỉ là P (x), trong đó P có nhiều thuật ngữ hơn P1 hoặc P2 .... vì vậy nó sẽ chậm hơn. Vì vậy, bước đầu tiên là xác định phần cứng của bạn và những gì nó có thể làm. Sau đó, bạn chọn kết hợp thích hợp của đa thức Ch Quashev (thường có dạng cos (ax) = aP (x) cho cosine chẳng hạn, một lần nữa trong đó P là đa thức Ch Quashev). Sau đó, bạn quyết định độ chính xác thập phân bạn muốn. ví dụ: nếu bạn muốn độ chính xác 7 chữ số, bạn tìm trong bảng thích hợp trong cuốn sách tôi đã đề cập và nó sẽ cung cấp cho bạn (với độ chính xác = 7,33) một số N = 4 và một số đa thức 3502. N là thứ tự của đa thức (vì vậy nó là p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0), vì N = 4. Sau đó, bạn tìm kiếm giá trị thực tế của p4, p3, p2, p1, giá trị p0 ở mặt sau của cuốn sách dưới 3502 (chúng sẽ ở điểm nổi). Sau đó, bạn triển khai thuật toán của mình trong phần mềm dưới dạng: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... và đây là cách bạn tính cosine đến 7 thập phân những nơi trên phần cứng đó.

Lưu ý rằng hầu hết các cài đặt phần cứng của các hoạt động siêu việt trong một FPU thường liên quan đến một số vi mã và các hoạt động như thế này (phụ thuộc vào phần cứng). Đa thức Ch Quashev được sử dụng cho hầu hết các siêu việt nhưng không phải tất cả. ví dụ: Căn bậc hai nhanh hơn khi sử dụng phương pháp lặp đôi của phương pháp Newton raphson bằng cách sử dụng bảng tra cứu trước tiên. Một lần nữa, cuốn sách "Xấp xỉ máy tính" sẽ cho bạn biết điều đó.

Nếu bạn có kế hoạch thực hiện các chức năng này, tôi khuyên mọi người nên lấy một bản sao của cuốn sách đó. Nó thực sự là kinh thánh cho các loại thuật toán. Lưu ý rằng có rất nhiều phương tiện thay thế để tính toán các giá trị này như các nguyên tắc, v.v., nhưng chúng có xu hướng tốt nhất cho các thuật toán cụ thể mà bạn chỉ cần độ chính xác thấp. Để đảm bảo độ chính xác mọi lúc, các đa thức ch Quashev là cách để đi. Như tôi đã nói, vấn đề được xác định rõ. Đã được giải quyết trong 50 năm nay ..... và đó là cách nó được thực hiện.

Bây giờ, điều đó đang được nói, có các kỹ thuật theo đó các đa thức Ch Quashev có thể được sử dụng để có được một kết quả chính xác duy nhất với đa thức bậc thấp (như ví dụ cho cosine ở trên). Sau đó, có các kỹ thuật khác để nội suy giữa các giá trị để tăng độ chính xác mà không cần phải đi đến một đa thức lớn hơn nhiều, chẳng hạn như "Phương pháp bảng chính xác của Gal". Kỹ thuật thứ hai này là những gì bài viết đề cập đến tài liệu ACM đang đề cập đến. Nhưng cuối cùng, Đa thức Ch Quashev là những gì được sử dụng để có được 90% trên đường đi.

Thưởng thức.


6
Tôi không thể đồng ý nhiều hơn với một vài câu đầu tiên. Ngoài ra, đáng để nhắc lại rằng tính toán các chức năng đặc biệt với độ chính xác được đảm bảo là một vấn đề khó . Những người thông minh mà bạn đề cập dành phần lớn cuộc đời của họ để làm điều này. Ngoài ra, trên một lưu ý kỹ thuật hơn, đa thức min-max là phần tử được tìm kiếm và đa thức Ch Quashev là các proxy đơn giản hơn cho chúng.
Alexandre C.

161
-1 cho giọng điệu không chuyên nghiệp và lan man (và hơi thô lỗ), và thực tế là nội dung không thừa của câu trả lời này, đã bị tước đi sự lan man và nhượng bộ, về cơ bản, họ thường sử dụng đa thức Ch Quashev; để biết thêm chi tiết, nó thực sự tốt! " Mà bạn biết đấy, có thể hoàn toàn chính xác, nhưng nó không thực sự là loại câu trả lời khép kín mà chúng ta muốn ở đây trên SO. Mặc dù cô đọng lại như vậy, nó sẽ đưa ra một nhận xét đúng đắn cho câu hỏi, mặc dù.
Ilmari Karonen

2
Quay trở lại những năm đầu phát triển trò chơi, nó thường được thực hiện với các bảng tra cứu cần thiết cho tốc độ). Chúng tôi thường không sử dụng các hàm lib tiêu chuẩn cho những thứ đó.
topspin

4
Tôi sử dụng các bảng tra cứu trong các hệ thống nhúng khá thường xuyên và bittian (thay vì radian), nhưng đây là một ứng dụng chuyên biệt (như các trò chơi của bạn). Tôi nghĩ rằng anh chàng quan tâm đến cách trình biên dịch c tính toán tội lỗi cho các số dấu phẩy động ....
Donald Murray

1
À, 50 năm trước. Tôi đã bắt đầu chơi với như vậy trên chiếc Burroughs B220 với dòng xe McLaren. Phần cứng CDC sau này và sau đó là Motorola 68000. Arcsin rất lộn xộn - Tôi đã chọn thương số của hai đa thức và mã được phát triển để tìm các hệ số tối ưu.
Rick James

15

Dành cho sin đặc biệt, sử dụng khai triển Taylor sẽ cung cấp cho bạn:

tội lỗi (x): = x - x ^ 3/3! + x ^ 5/5! - x ^ 7/7! + ... (1)

bạn sẽ tiếp tục thêm các điều khoản cho đến khi mức chênh lệch giữa chúng thấp hơn mức dung sai được chấp nhận hoặc chỉ cho một số bước hữu hạn (nhanh hơn, nhưng kém chính xác hơn). Một ví dụ sẽ là một cái gì đó như:

float sin(float x)
{
  float res=0, pow=x, fact=1;
  for(int i=0; i<5; ++i)
  {
    res+=pow/fact;
    pow*=-1*x*x;
    fact*=(2*(i+1))*(2*(i+1)+1);
  }

  return res;
}

Lưu ý: (1) hoạt động vì sin apro xấp xỉ (x) = x cho các góc nhỏ. Đối với các góc lớn hơn, bạn cần tính toán nhiều thuật ngữ hơn để có kết quả chấp nhận được. Bạn có thể sử dụng đối số while và tiếp tục cho độ chính xác nhất định:

double sin (double x){
    int i = 1;
    double cur = x;
    double acc = 1;
    double fact= 1;
    double pow = x;
    while (fabs(acc) > .00000001 &&   i < 100){
        fact *= ((2*i)*(2*i+1));
        pow *= -1 * x*x; 
        acc =  pow / fact;
        cur += acc;
        i++;
    }
    return cur;

}

1
Nếu bạn điều chỉnh các hệ số một chút (và mã cứng chúng thành đa thức), bạn có thể dừng khoảng 2 lần lặp lại sớm hơn.
Rick James

14

Vâng, có các thuật toán phần mềm để tính toán sinquá. Về cơ bản, việc tính toán các loại công cụ này bằng máy tính kỹ thuật số thường được thực hiện bằng các phương pháp số như xấp xỉ chuỗi Taylor đại diện cho chức năng.

Các phương pháp số có thể xấp xỉ các hàm với độ chính xác tùy ý và vì độ chính xác mà bạn có trong một số trôi nổi là hữu hạn, chúng phù hợp với các tác vụ này khá tốt.


12
Một triển khai thực sự có thể sẽ không sử dụng loạt Taylor, vì có nhiều cách hiệu quả hơn. Bạn chỉ cần ước chừng chính xác trong miền [0 ... pi / 2], và có các chức năng sẽ cung cấp một xấp xỉ tốt hiệu quả hơn so với loạt Taylor.
David Thornley

2
@David: Tôi đồng ý. Tôi đã đủ cẩn thận để đề cập đến từ "thích" trong câu trả lời của mình. Nhưng Taylor mở rộng là một cách đơn giản để giải thích ý tưởng đằng sau các phương thức gần đúng chức năng. Điều đó nói rằng, tôi đã thấy việc triển khai phần mềm (không chắc chúng có được tối ưu hóa không) đã sử dụng loạt Taylor.
Mehrdad Afshari

1
Trên thực tế, xấp xỉ đa thức là một trong những cách hiệu quả nhất để tính các hàm lượng giác.
Jeremy Salwen

13

Sử dụng chuỗi Taylor và cố gắng tìm mối quan hệ giữa các điều khoản của chuỗi để bạn không tính toán mọi thứ nhiều lần

Dưới đây là một ví dụ cho cosinus:

double cosinus(double x, double prec)
{
    double t, s ;
    int p;
    p = 0;
    s = 1.0;
    t = 1.0;
    while(fabs(t/s) > prec)
    {
        p++;
        t = (-t * x * x) / ((2 * p - 1) * (2 * p));
        s += t;
    }
    return s;
}

bằng cách này, chúng ta có thể nhận được số hạng mới của tổng bằng cách sử dụng số đã sử dụng (chúng ta tránh giai thừa và x 2p )

giải trình


2
Bạn có biết bạn có thể sử dụng API Google Biểu đồ để tạo các công thức như thế này bằng TeX không? code.google.com/apis/chart/docs/gallery/form Formula.html
Gab Royer

11

Đó là một câu hỏi phức tạp. CPU giống như Intel của họ x86 có sin()chức năng triển khai phần cứng , nhưng nó là một phần của FPU x87 và không được sử dụng nữa trong chế độ 64 bit (thay vào đó sử dụng các thanh ghi SSE2). Trong chế độ đó, một triển khai phần mềm được sử dụng.

Có một số thực hiện như vậy ra khỏi đó. Một là trong fdlibm và được sử dụng trong Java. Theo như tôi biết, việc triển khai glibc chứa các phần của fdlibm và các phần khác do IBM đóng góp.

Phần mềm triển khai các chức năng siêu việt như sin()thường sử dụng xấp xỉ bằng đa thức, thường thu được từ chuỗi Taylor.


3
Các thanh ghi SSE2 không được sử dụng để tính sin (), không ở chế độ x86 cũng như ở chế độ x64 và tất nhiên, sin được tính trong phần cứng bất kể chế độ. Này, đó là năm 2010 chúng ta đang sống :)
Igor Korkhov

7
@Igor: điều đó phụ thuộc vào thư viện toán học mà bạn đang xem. Nó chỉ ra rằng các thư viện toán học được tối ưu hóa nhất trên x86 sử dụng các triển khai phần mềm SSE sincosnhanh hơn các hướng dẫn phần cứng trên FPU. Đơn giản hơn, các thư viện ngây thơ hơn có xu hướng sử dụng fsinfcoshướng dẫn.
Stephen Canon

@Stephen Canon: Những thư viện nhanh đó có độ chính xác 80 bit như các thanh ghi FPU không? Tôi có một nghi ngờ rất lén lút rằng họ thiên về tốc độ hơn độ chính xác, điều này tất nhiên là hợp lý trong nhiều tình huống, ví dụ như trong các trò chơi. Và tôi tin rằng việc tính toán sin với độ chính xác 32 bit bằng cách sử dụng SSE và các bảng trung gian được tính toán trước có thể nhanh hơn so với sử dụng FSINvới độ chính xác đầy đủ. Tôi sẽ rất biết ơn nếu bạn cho tôi biết tên của những thư viện nhanh đó, thật thú vị khi xem qua.
Igor Korkhov

@Igor: trên x86 ở chế độ 64 bit, ít nhất là trên tất cả các hệ thống giống như Unix mà tôi biết, độ chính xác bị giới hạn ở 64 bit, không phải là 79 bit của FPU x87. Việc triển khai phần mềm sin()xảy ra nhanh hơn khoảng hai lần so với những gì fsintính toán (chính xác là do nó được thực hiện với độ chính xác thấp hơn). Lưu ý rằng x87 được biết là có độ chính xác thực tế ít hơn một chút so với 79 bit được công bố.
Thomas Pornin

1
Thật vậy, cả hai triển khai sin () 32 bit và 64 bit trong các thư viện thời gian chạy msvc đều không sử dụng lệnh FSIN. Trong thực tế, họ cho kết quả khác nhau, ví dụ tội lỗi (0,70444454416678126). Điều này sẽ dẫn đến 0,64761068800896837 (phải giảm 0,5 * (eps / 2)) trong chương trình 32 bit và sẽ tạo ra 0,64761068800896848 (sai) trong 64 bit.
e.tadeu

9

Đa thức Ch Quashev, như đã đề cập trong một câu trả lời khác, là các đa thức trong đó sự khác biệt lớn nhất giữa hàm và đa thức càng nhỏ càng tốt. Đó là một khởi đầu tuyệt vời.

Trong một số trường hợp, lỗi tối đa không phải là điều bạn quan tâm, mà là lỗi tương đối tối đa. Ví dụ, đối với hàm sin, sai số gần x = 0 phải nhỏ hơn nhiều so với các giá trị lớn hơn; bạn muốn một lỗi tương đối nhỏ . Vì vậy, bạn sẽ tính đa thức Ch Quashev cho sin x / x và nhân đa thức đó với x.

Tiếp theo bạn phải tìm ra cách đánh giá đa thức. Bạn muốn đánh giá nó theo cách sao cho các giá trị trung gian nhỏ và do đó sai số làm tròn là nhỏ. Nếu không, các lỗi làm tròn có thể trở nên lớn hơn rất nhiều so với các lỗi trong đa thức. Và với các hàm như hàm sin, nếu bạn bất cẩn thì có thể kết quả mà bạn tính cho sin x lớn hơn kết quả cho sin y ngay cả khi x <y. Vì vậy, lựa chọn cẩn thận thứ tự tính toán và tính toán giới hạn trên cho lỗi làm tròn là cần thiết.

Ví dụ: sin x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/4040 ... Nếu bạn tính sin sin x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - x ^ 6/5040 ...), thì hàm đó trong ngoặc đơn đang giảm và sẽ xảy ra nếu y là số lớn hơn tiếp theo với x, thì đôi khi sin y sẽ nhỏ hơn sin x. Thay vào đó, hãy tính sin x = x - x ^ 3 * (1/6 - x ^ 2/20 + x ^ 4/5040 ...) khi điều này không thể xảy ra.

Khi tính toán các đa thức Ch Quashev, bạn thường cần làm tròn các hệ số để độ chính xác gấp đôi, ví dụ. Nhưng trong khi một đa thức Ch Quashev là tối ưu, thì đa thức Ch Quashev với các hệ số được làm tròn đến độ chính xác kép không phải là đa thức tối ưu với các hệ số chính xác kép!

Ví dụ: sin (x), trong đó bạn cần các hệ số cho x, x ^ 3, x ^ 5, x ^ 7, v.v. bạn làm như sau: Tính xấp xỉ tốt nhất của sin x với đa thức (ax + bx ^ 3 + cx ^ 5 + dx ^ 7) với độ chính xác cao hơn gấp đôi, sau đó làm tròn độ chính xác đến gấp đôi, cho A. Sự khác biệt giữa a và A sẽ khá lớn. Bây giờ hãy tính xấp xỉ tốt nhất của (sin x - Ax) với đa thức (bx ^ 3 + cx ^ 5 + dx ^ 7). Bạn nhận được các hệ số khác nhau, bởi vì chúng thích ứng với sự khác biệt giữa a và A. Làm tròn chính xác đến gấp đôi B. Sau đó xấp xỉ (sin x - Ax - Bx ^ 3) với đa thức cx ^ 5 + dx ^ 7, v.v. Bạn sẽ nhận được một đa thức gần như tương đương với đa thức Ch Quashev ban đầu, nhưng tốt hơn nhiều so với Ch Quashev được làm tròn với độ chính xác gấp đôi.

Tiếp theo, bạn nên tính đến các lỗi làm tròn trong việc lựa chọn đa thức. Bạn đã tìm thấy một đa thức có lỗi tối thiểu trong đa thức bỏ qua lỗi làm tròn, nhưng bạn muốn tối ưu hóa đa thức cộng với lỗi làm tròn. Khi bạn có đa thức Ch Quashev, bạn có thể tính giới hạn cho lỗi làm tròn. Giả sử f (x) là hàm của bạn, P (x) là đa thức và E (x) là lỗi làm tròn. Bạn không muốn tối ưu hóa | f (x) - P (x) |, bạn muốn tối ưu hóa | f (x) - P (x) +/- E (x) |. Bạn sẽ nhận được một đa thức hơi khác nhau, cố gắng giữ các lỗi đa thức ở nơi sai số làm tròn lớn và làm giảm các lỗi đa thức một chút trong đó sai số làm tròn nhỏ.

Tất cả điều này sẽ giúp bạn dễ dàng làm tròn các lỗi nhiều nhất 0,55 lần bit cuối cùng, trong đó +, -, *, / có các lỗi làm tròn nhiều nhất là 0,5 lần so với bit cuối cùng.


1
Đây là một lời giải thích hay về cách người ta có thể tính sin (x) một cách hiệu quả, nhưng dường như không thực sự trả lời câu hỏi của OP, mà cụ thể là về cách các thư viện / trình biên dịch C phổ biến thực hiện tính toán nó.
Ilmari Karonen

Đa thức Ch Quashev tối thiểu hóa giá trị tuyệt đối tối đa trong một khoảng, nhưng chúng không giảm thiểu sự khác biệt lớn nhất giữa hàm mục tiêu và đa thức. Đa thức Minimax làm điều đó.
Eric Postpischil

9

Liên quan đến hàm lượng giác như sin(), cos(), tan()đã có không đề cập đến, sau 5 năm, trong một khía cạnh quan trọng của chức năng trang điểm chất lượng cao: giảm Phạm vi .

Bước đầu tiên trong bất kỳ chức năng nào trong số này là giảm góc, tính bằng radian, xuống một phạm vi trong khoảng 2 * π. Nhưng π là không hợp lý nên các mức giảm đơn giản như x = remainder(x, 2*M_PI)giới thiệu lỗi như M_PI, hoặc máy pi, là một xấp xỉ của π. Vậy, phải làm x = remainder(x, 2*π)thế nào?

Các thư viện ban đầu đã sử dụng chính xác mở rộng hoặc lập trình thủ công để cho kết quả chất lượng nhưng vẫn trong một phạm vi hạn chế double. Khi một giá trị lớn được yêu cầu như thế sin(pow(2,30)), kết quả là vô nghĩa hoặc 0.0có thể với một cờ lỗi được đặt thành một cái gì đó như TLOSSmất hoàn toàn độ chính xác hoặc PLOSSmất một phần độ chính xác.

Giảm phạm vi tốt của các giá trị lớn xuống một khoảng như -π đến π là một vấn đề thách thức đối nghịch với các thách thức của hàm trig cơ bản, như sin(), chính nó.

Một báo cáo tốt là Giảm đối số cho các đối số lớn: Tốt đến bit cuối cùng (1992). Nó bao gồm vấn đề này cũng như: thảo luận về sự cần thiết và làm thế nào mọi thứ trên nền tảng khác nhau (SPARC, PC, HP, 30 + khác) và cung cấp một thuật toán giải pháp này cung cấp cho kết quả chất lượng cho tất cả các double từ -DBL_MAXđể DBL_MAX.


Nếu các đối số ban đầu được tính theo độ, nhưng có thể có giá trị lớn, fmod()trước tiên hãy sử dụng để cải thiện độ chính xác. Một điều tốt fmod()sẽ giới thiệu không có lỗi và do đó cung cấp giảm phạm vi tuyệt vời.

// sin(degrees2radians(x))
sin(degrees2radians(fmod(x, 360.0))); // -360.0 < fmod(x,360) < +360.0

Nhận dạng trig khác nhau và remquo()cung cấp cải tiến hơn nữa. Mẫu: sind ()


6

Việc triển khai thực tế các chức năng của thư viện tùy thuộc vào trình biên dịch và / hoặc nhà cung cấp thư viện cụ thể. Cho dù nó được thực hiện trong phần cứng hay phần mềm, cho dù đó là bản mở rộng Taylor hay không, v.v., sẽ khác nhau.

Tôi nhận ra rằng điều đó hoàn toàn không có ích.


5

Chúng thường được triển khai trong phần mềm và sẽ không sử dụng các cuộc gọi phần cứng tương ứng (nghĩa là lắp ráp) trong hầu hết các trường hợp. Tuy nhiên, như Jason chỉ ra, đây là những triển khai cụ thể.

Lưu ý rằng các thường trình phần mềm này không phải là một phần của các nguồn biên dịch, nhưng sẽ được tìm thấy trong thư viện sửa lỗi như clib hoặc glibc cho trình biên dịch GNU. Xem http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Fifts

Nếu bạn muốn kiểm soát tốt hơn, bạn nên đánh giá cẩn thận những gì bạn cần chính xác. Một số phương pháp điển hình là nội suy các bảng tra cứu, lệnh gọi lắp ráp (thường chậm) hoặc các sơ đồ gần đúng khác như Newton-Raphson cho căn bậc hai.


5

Nếu bạn muốn triển khai phần mềm, không phải phần cứng, nơi để tìm câu trả lời dứt khoát cho câu hỏi này là Chương 5 của Công thức toán số . Bản sao của tôi nằm trong một hộp, vì vậy tôi không thể cung cấp chi tiết, nhưng phiên bản ngắn (nếu tôi nhớ đúng) là bạn thực hiện tan(theta/2)như thao tác nguyên thủy của mình và tính toán những cái khác từ đó. Việc tính toán được thực hiện với một xấp xỉ chuỗi, nhưng đó là thứ hội tụ nhiều nhanh hơn một loạt Taylor.

Xin lỗi tôi không thể nhớ lại nhiều hơn mà không có trong tay cuốn sách.


5

Không có gì giống như nhấn vào nguồn và xem cách ai đó thực sự đã làm nó trong một thư viện được sử dụng phổ biến; Hãy nhìn vào một triển khai thư viện C nói riêng. Tôi đã chọn uLibC.

Đây là chức năng tội lỗi:

http://git.uclibc.org/uClibc/tree/libm/s_sin.c

có vẻ như nó xử lý một vài trường hợp đặc biệt và sau đó thực hiện một số giảm đối số để ánh xạ đầu vào vào phạm vi [-pi / 4, pi / 4], (chia đối số thành hai phần, phần lớn và phần đuôi) trước khi gọi

http://git.uclibc.org/uClibc/tree/libm/k_sin.c

mà sau đó hoạt động trên hai phần đó. Nếu không có đuôi, một câu trả lời gần đúng được tạo ra bằng đa thức bậc 13. Nếu có đuôi, bạn có được một phép cộng nhỏ dựa trên nguyên tắcsin(x+y) = sin(x) + sin'(x')y


4

Bất cứ khi nào một chức năng như vậy được đánh giá, thì ở một mức độ nào đó rất có thể là:

  • Một bảng các giá trị được nội suy (cho các ứng dụng nhanh, không chính xác - ví dụ: đồ họa máy tính)
  • Việc đánh giá một chuỗi hội tụ đến giá trị mong muốn --- có thể không phải là một chuỗi taylor, nhiều khả năng là một cái gì đó dựa trên một hình cầu lạ mắt như Clenshaw-Curtis.

Nếu không có hỗ trợ phần cứng thì trình biên dịch có thể sử dụng phương thức sau, chỉ phát ra mã trình biên dịch mã (không có ký hiệu gỡ lỗi), thay vì sử dụng thư viện ac --- khiến bạn khó theo dõi mã thực tế trong trình gỡ lỗi.


4

Như nhiều người chỉ ra, nó phụ thuộc vào việc thực hiện. Nhưng theo như tôi hiểu câu hỏi của bạn, bạn đã quan tâm đến một phần mềm thực sự bao hàm các hàm toán học, nhưng chỉ không tìm được. Nếu đây là trường hợp thì đây là:

  • Tải xuống mã nguồn glibc từ http://ftp.gnu.org/gnu/glibc/
  • Nhìn vào tập tin dosincos.cnằm trong thư mục glibc root \ sysdeps \ ieee754 \ dbl-64
  • Tương tự như vậy, bạn có thể tìm thấy các triển khai của phần còn lại của thư viện toán học, chỉ cần tìm tệp có tên thích hợp

Bạn cũng có thể xem các tệp có .tblphần mở rộng, nội dung của chúng không có gì khác ngoài các bảng giá trị lớn được tính toán trước của các hàm khác nhau ở dạng nhị phân. Đó là lý do tại sao việc triển khai quá nhanh: thay vì tính toán tất cả các hệ số của bất kỳ chuỗi nào họ sử dụng, họ chỉ cần thực hiện tra cứu nhanh, nhanh hơn nhiều . BTW, họ sử dụng chuỗi Tailor để tính sin và cos.

Tôi hi vọng cái này giúp được.


4

Tôi sẽ cố gắng trả lời cho trường hợp sin()trong chương trình C, được biên dịch bằng trình biên dịch C của GCC trên bộ xử lý x86 hiện tại (giả sử Intel Core 2 Duo).

Trong ngôn ngữ C, Thư viện C tiêu chuẩn bao gồm các hàm toán học thông thường, không bao gồm trong chính ngôn ngữ đó (ví dụ pow, sincoscho sức mạnh, sin và cos tương ứng). Các tiêu đề được bao gồm trong math.h .

Bây giờ trên hệ thống GNU / Linux, các chức năng thư viện này được cung cấp bởi glibc (GNU libc hoặc GNU C Library). Nhưng trình biên dịch GCC muốn bạn liên kết đến thư viện toán học ( libm.so) bằng cách sử dụng -lmcờ trình biên dịch để cho phép sử dụng các hàm toán học này. Tôi không chắc tại sao nó không phải là một phần của thư viện C tiêu chuẩn. Đây sẽ là phiên bản phần mềm của các chức năng dấu phẩy động hoặc "phao mềm".

Ngoài ra: Lý do để tách các hàm toán học là lịch sử và chỉ nhằm mục đích giảm kích thước của các chương trình thực thi trong các hệ thống Unix rất cũ, có thể trước khi các thư viện chia sẻ có sẵn, theo như tôi biết.

Bây giờ trình biên dịch có thể tối ưu hóa chức năng thư viện C tiêu chuẩn sin()(được cung cấp bởi libm.so) để được thay thế bằng một lệnh gọi đến một lệnh gốc đến hàm sin () tích hợp của CPU / FPU, tồn tại dưới dạng một lệnh FPU ( FSINcho x86 / x87) trên các bộ xử lý mới hơn như dòng Core 2 (điều này đúng khá nhiều so với i486DX). Điều này phụ thuộc vào các cờ tối ưu hóa được chuyển đến trình biên dịch gcc. Nếu trình biên dịch được yêu cầu viết mã sẽ thực thi trên bất kỳ bộ xử lý i386 hoặc mới hơn, nó sẽ không thực hiện tối ưu hóa như vậy. Các -mcpu=486lá cờ sẽ thông báo cho trình biên dịch rằng nó là an toàn để làm như vậy tối ưu hóa.

Bây giờ nếu chương trình thực hiện các phiên bản phần mềm của sin () chức năng, nó sẽ làm như vậy dựa trên một CORDIC (phối hợp Rotation Digital Computer) hoặc BKM thuật toán , hoặc nhiều hơn có thể là một bảng hoặc điện-series tính được sử dụng phổ biến hiện nay để tính toán chức năng siêu việt như vậy. [Src: http://en.wikipedia.org/wiki/Cordic#Application]

Bất kỳ phiên bản gcc nào gần đây (kể từ 2,9 lần) cũng cung cấp phiên bản tội lỗi tích hợp, __builtin_sin()nó sẽ được sử dụng để thay thế cuộc gọi tiêu chuẩn đến phiên bản thư viện C, như một sự tối ưu hóa.

Tôi chắc chắn rằng nó rõ ràng như bùn, nhưng hy vọng cung cấp cho bạn nhiều thông tin hơn bạn mong đợi, và rất nhiều điểm để tìm hiểu thêm về bản thân.



3

Đừng sử dụng loạt Taylor. Đa thức Ch Quashev đều nhanh hơn và chính xác hơn, như được chỉ ra bởi một vài người ở trên. Đây là một triển khai (ban đầu từ ROM ZX Spectrum): https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
Điều này dường như không thực sự trả lời câu hỏi như đã hỏi. OP được hỏi làm thế nào các chức năng trang điểm được tính bằng trình biên dịch C common / thư viện (và tôi khá chắc chắn ZX Spectrum không đủ điều kiện), không phải là cách họ nên được tính toán. Điều này có thể đã là một nhận xét hữu ích về một số câu trả lời trước đó, mặc dù.
Ilmari Karonen

1
À, bạn nói đúng. Nó nên là một bình luận và không phải là một câu trả lời. Tôi đã không sử dụng SO trong một thời gian và quên cách hệ thống hoạt động. Dù sao, tôi nghĩ việc triển khai Spectrum có liên quan vì nó có CPU thực sự chậm và tốc độ là điều cốt yếu. Thuật toán tốt nhất sau đó chắc chắn vẫn còn khá tốt, do đó, sẽ là một ý tưởng tốt cho các thư viện C để thực hiện các hàm trig bằng cách sử dụng đa thức Ch Quashev.
Albert Veli

2

Việc tính toán sin / cosine / tiếp tuyến thực sự rất dễ thực hiện thông qua mã sử dụng chuỗi Taylor. Viết một bản thân bạn mất 5 giây.

Toàn bộ quá trình có thể được tóm tắt với phương trình này ở đây:

tội lỗi và mở rộng chi phí

Dưới đây là một số thói quen tôi đã viết cho C:

double _pow(double a, double b) {
    double c = 1;
    for (int i=0; i<b; i++)
        c *= a;
    return c;
}

double _fact(double x) {
    double ret = 1;
    for (int i=1; i<=x; i++) 
        ret *= i;
    return ret;
}

double _sin(double x) {
    double y = x;
    double s = -1;
    for (int i=3; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _cos(double x) {
    double y = 1;
    double s = -1;
    for (int i=2; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _tan(double x) {
     return (_sin(x)/_cos(x));  
}

4
Đây là một triển khai khá tệ vì nó không sử dụng các thuật ngữ nối tiếp của chuỗi sin và cos có các chỉ số rất đơn giản. Điều đó có nghĩa là người ta có thể giảm số lượng nhân và chia từ O (n ^ 2) ở đây xuống O (n). Việc giảm thêm được thực hiện bằng cách giảm một nửa và bình phương, ví dụ như nó được thực hiện trong thư viện toán học bc (POSIX multrecision).
Lutz Lehmann

2
Nó cũng dường như không trả lời câu hỏi như đã hỏi; OP đang hỏi làm thế nào các hàm trig được tính toán bởi các trình biên dịch / thư viện C thông thường, không phải cho việc thực hiện lại tùy chỉnh.
Ilmari Karonen

2
Tôi nghĩ rằng đó là một câu trả lời tốt vì nó trả lời tinh thần của câu hỏi mà (và tôi chỉ có thể đoán được tất nhiên) sự tò mò về một chức năng "hộp đen" khác như sin (). Đây là câu trả lời duy nhất ở đây giúp người ta có cơ hội nhanh chóng hiểu những gì đang xảy ra bằng cách che đậy nó trong vài giây thay vì đọc một số mã nguồn C được tối ưu hóa.
Mike M

trong thực tế, các thư viện sử dụng phiên bản được tối ưu hóa hơn nhiều, bằng cách nhận ra rằng một khi bạn có một thuật ngữ, bạn có thể có được thuật ngữ tiếp theo bằng cách nhân một số giá trị. Xem một ví dụ trong câu trả lời của Blindy . Bạn đang tính toán sức mạnh và giai thừa nhiều lần chậm hơn
phuclv


0

Phiên bản cải tiến của mã từ câu trả lời của Blindy

#define EPSILON .0000000000001
// this is smallest effective threshold, at least on my OS (WSL ubuntu 18)
// possibly because factorial part turns 0 at some point
// and it happens faster then series element turns 0;
// validation was made against sin() from <math.h>
double ft_sin(double x)
{
    int k = 2;
    double r = x;
    double acc = 1;
    double den = 1;
    double num = x;

//  precision drops rapidly when x is not close to 0
//  so move x to 0 as close as possible
    while (x > PI)
        x -= PI;
    while (x < -PI)
        x += PI;
    if (x > PI / 2)
        return (ft_sin(PI - x));
    if (x < -PI / 2)
        return (ft_sin(-PI - x));
//  not using fabs for performance reasons
    while (acc > EPSILON || acc < -EPSILON)
    {
        num *= -x * x;
        den *= k * (k + 1);
        acc = num / den;
        r += acc;
        k += 2;
    }
    return (r);
}

0

Bản chất của cách nó thực hiện điều này nằm trong đoạn trích từ Phân tích số ứng dụng của Gerald Wheatley:

Khi chương trình phần mềm của bạn yêu cầu máy tính nhận giá trị nhập mô tả hình ảnh ở đâyhoặc nhập mô tả hình ảnh ở đây, bạn có tự hỏi làm thế nào nó có thể nhận được các giá trị nếu các hàm mạnh nhất mà nó có thể tính toán là đa thức? Nó không nhìn lên trong bảng và nội suy! Thay vào đó, máy tính xấp xỉ mọi hàm khác với đa thức từ một số đa thức được thiết kế để đưa ra các giá trị rất chính xác.

Một vài điểm cần đề cập ở trên là một số thuật toán thực hiện nội suy từ một bảng, mặc dù chỉ cho một vài lần lặp đầu tiên. Cũng lưu ý cách nó đề cập rằng máy tính sử dụng đa thức gần đúng mà không chỉ định loại đa thức gần đúng nào. Như những người khác trong chủ đề đã chỉ ra, đa thức Ch Quashev có hiệu quả hơn đa thức Taylor trong trường hợp này.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.