Về xấp xỉ nhanh hơn của log (x)


10

Tôi đã viết một mã trước đây để tính mà không sử dụng các hàm thư viện. Hôm qua, tôi đã xem lại mã cũ và tôi đã cố gắng làm cho nó nhanh nhất có thể, (và chính xác). Đây là nỗ lực của tôi cho đến nay:log(x)

const double ee = exp(1);

double series_ln_taylor(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 )
        n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(1 - x) = -x - x**2/2 - x**3/3... */
    n = 1 - n;
    now = term = n;
    for ( i = 1 ; ; ){
        lgVal -= now;
        term *= n;
        now = term / ++i;
        if ( now < 1e-17 ) break;
    }

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

Ở đây tôi đang cố gắng tìm cái sao cho chỉ hơn n, và sau đó tôi thêm giá trị logarit của \ frac {n} {e ^ {a}} , nhỏ hơn 1. Tại thời điểm này, việc mở rộng Taylor của nhật ký (1 \ - \ x) có thể được sử dụng mà không phải lo lắng.anea log(1-x)nealog(1  x)

Gần đây tôi đã có hứng thú với phân tích số và đó là lý do tại sao tôi không thể giúp đặt câu hỏi, đoạn mã này có thể chạy nhanh hơn bao nhiêu trong thực tế, trong khi đủ chính xác? Tôi có cần chuyển sang một số phương pháp khác không, ví dụ, sử dụng phân số tiếp tục, như thế này ?

Hàm được cung cấp với thư viện chuẩn C nhanh hơn gần 5,1 lần so với triển khai này.log(x)

CẬP NHẬT 1 : Sử dụng chuỗi arctan hyperbol được đề cập trong Wikipedia , tính toán dường như chậm hơn gần 2,2 lần so với chức năng nhật ký thư viện chuẩn C. Mặc dù, tôi đã không kiểm tra rộng rãi hiệu suất và đối với số lượng lớn hơn, việc triển khai hiện tại của tôi dường như thực sự chậm. Tôi muốn kiểm tra cả hai triển khai của mình để biết lỗi bị ràng buộc và thời gian trung bình cho một phạm vi số rộng nếu tôi có thể quản lý. Đây là nỗ lực thứ hai của tôi.

double series_ln_arctanh(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;
    for ( i = 3 ; ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
       if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;
    return lgVal;
}

Bất kỳ đề nghị hoặc phê bình đều được đánh giá cao.

CẬP NHẬT 2: Dựa trên các đề xuất được đưa ra dưới đây, tôi đã thêm một số thay đổi gia tăng ở đây, chậm hơn khoảng 2,5 lần so với triển khai thư viện tiêu chuẩn. Tuy nhiên, tôi đã thử nghiệm nó chỉ cho các số nguyên lần này, với số lượng lớn hơn thời gian chạy sẽ tăng lên. Để bây giờ. Tôi chưa biết các kỹ thuật để tạo số kép ngẫu nhiên , vì vậy nó chưa được điểm chuẩn đầy đủ. Để làm cho mã mạnh mẽ hơn, tôi đã thêm các sửa chữa cho các trường hợp góc. Lỗi trung bình cho các bài kiểm tra tôi thực hiện là khoảng .1 e 308 4 đ - 151e81e3084e15

double series_ln_better(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n == 0 ) return -1./0.; /* -inf */
    if ( n < 0 ) return 0./0.;   /* NaN*/
    if ( n < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    /* the cutoff iteration is 650, as over e**650, term multiplication would
       overflow. For larger numbers, the loop dominates the arctanh approximation
       loop (with having 13-15 iterations on average for tested numbers so far */

    for ( term = 1; term < n && lgVal < 650 ; term *= ee, lgVal++ );
    if ( lgVal == 650 ){
        n /= term;
        for ( term = 1 ; term < n ; term *= ee, lgVal++ );
    }
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;

    /* limiting the iteration for worst case scenario, maximum 24 iteration */
    for ( i = 3 ; i < 50 ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
        if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

Câu trả lời:


17

Đây thực sự không phải là một câu trả lời có thẩm quyền , thêm một danh sách các vấn đề tôi nghĩ bạn nên xem xét và tôi đã không kiểm tra mã của bạn.

0. Làm thế nào bạn kiểm tra mã của bạn cho chính xác và tốc độ? Cả hai đều quan trọng, hơi khó để làm tốt và bạn không cung cấp chi tiết. Nói cách khác, nếu tôi so sánh chức năng của bạn với máy log trên máy của mình , tôi cũng sẽ nhận được các số tương tự, , chứ? Theo kinh nghiệm của tôi với việc đọc điểm chuẩn thời gian của người khác trong tài liệu học thuật, nó đòi hỏi rất nhiều sự cẩn thận và chính xác để có được thời gian có thể lặp lại , đây là thời gian duy nhất mà bất cứ ai cũng quan tâm. Microbenchmark đặc biệt là không đáng tin cậy.5.12.15.1

1. Vấn đề phổ biến khi đánh giá một hàm trực tiếp với chuỗi Taylor (chưa sửa đổi) của nó là số lượng thuật ngữ cần thiết để hội tụ. Có 52 bit trong phần tử của a , vì vậy khi ở đầu vòng lặp Taylor, bạn có thể mong đợi vòng lặp sẽ mất khoảng 50 lần lặp. Điều đó khá tốn kém và nên được tối ưu hóa.n 1f(x)doublen12

1.5. Bạn đã kiểm tra mã của bạn cho lớn ? Tôi đã thử , dẫn đến , và sau đó trong vòng lặp Taylor, dẫn đến sự hội tụ cực kỳ chậm: nó hội tụ như loạt Harmonic, tức là không, nhưng sẽ có nhiều nhất là . Theo nguyên tắc thông thường, bạn nên có một loại "số lần lặp tối đa" bị ràng buộc cho vòng lặp. Trong trường hợp này, nó hoạt động như thế này vì là hữu hạn, nhưng tràn ra trong vòng lặp giảm đối số. Câu trả lời đúng là .10 17 n 709,78266108405500745 ...n1.7976e+308term=infn=11017nterm *= e709.78266108405500745

2. Các chức năng được thực hiện trong các thư viện tiêu chuẩn dự kiến ​​sẽ cực kỳ mạnh mẽ. Trả về ( ) vì logarit của số âm (hoặc không) là không chính xác. Logarit của phải là , logarit của một số âm phải là NaN.0 0 - 1030000

Tôi nghi ngờ rằng với một chút nỗ lực, bạn có thể hy sinh một số sự mạnh mẽ đó để thực hiện, ví dụ, bằng cách hạn chế phạm vi đối số hoặc trả về kết quả kém chính xác hơn một chút.

3. Hiệu suất của loại mã này có thể phụ thuộc rất nhiều vào kiến ​​trúc CPU mà nó đang chạy. Đây là một chủ đề sâu sắc và có liên quan, nhưng các nhà sản xuất CPU như Intel xuất bản các hướng dẫn tối ưu hóa giải thích các tương tác khác nhau giữa mã của bạn và CPU mà nó đang chạy. Bộ nhớ đệm có thể tương đối đơn giản, nhưng những thứ như dự đoán nhánh, song song mức hướng dẫn và quầy hàng đường ống do phụ thuộc dữ liệu rất khó nhìn thấy chính xác trong mã cấp cao, nhưng rất quan trọng đối với hiệu suất.

4. Thực hiện một chức năng như thế này một cách chính xác thường có nghĩa là bạn đảm bảo rằng đối với số dấu phẩy động đầu vào , đầu ra nằm trong một khoảng cách nhất định của điểm nổi gần nhất số đến giá trị thực . Xác minh điều này không hoàn toàn tầm thường, không có bằng chứng nào trong mã của bạn đã thực hiện điều này, vì vậy tôi không biết chức năng của bạn có đúng không (tôi chắc chắn nó khá chính xác, nhưng chính xác đến mức nào ?). Điều này không giống như cho thấy rằng chuỗi Taylor hội tụ, do sự hiện diện của các lỗi vòng tròn dấu phẩy động.˜ y = ˜ f ( ˜ x )y=f( ˜ x )x~y~=f~(x~)y=f(x~)

4.5. Một cách tốt để kiểm tra độ chính xác của hàm chưa được kiểm tra, sẽ là đánh giá hàm đó ở một trong bốn tỷ (ít hơn nếu bạn thực hiện giảm đối số chính xác, như ở đây) thả nổi chính xác và so sánh các lỗi với nhật ký chuẩn từ libm. Mất một chút thời gian, nhưng ít nhất là triệt để.

5. Vì bạn biết ngay từ đầu độ chính xác của đôi, bạn không cần phải có một vòng lặp không giới hạn: số lần lặp có thể được tìm ra ở phía trước (có thể là khoảng 50). Sử dụng công cụ này để xóa các nhánh khỏi mã của bạn hoặc ít nhất là đặt số lần lặp trước.

Tất cả các ý tưởng thông thường về unrolling vòng lặp áp dụng quá.

6. Có thể sử dụng các kỹ thuật gần đúng khác với loạt Taylor. Ngoài ra còn có các chuỗi Ch Quashev (với tái phát Clenshaw), các xấp xỉ Pade và đôi khi các phương pháp tìm gốc như phương pháp của Newton bất cứ khi nào hàm của bạn có thể được gọi lại là gốc của hàm đơn giản hơn (ví dụ: thủ thuật sqrt nổi tiếng ).

Các phân số tiếp tục có lẽ sẽ không quá lớn, bởi vì chúng liên quan đến phân chia, đắt hơn nhiều so với bội số / cộng. Nếu bạn nhìn vào _mm_div_sstại https://software.intel.com/sites/landingpage/IntrinsicsGuide/ , chia có độ trễ của 13-14 chu kỳ và thông lượng 5-14, tùy thuộc vào kiến trúc, so với 3-5 / 0,5-1 để nhân / thêm / madd. Vì vậy, nói chung (không phải luôn luôn), nên cố gắng loại bỏ sự phân chia càng nhiều càng tốt.

Thật không may, toán học không phải là như vậy một hướng dẫn tuyệt vời ở đây, bởi vì biểu với ngắn công thức không nhất thiết phải là những người nhanh nhất. Toán học không phạt các bộ phận, ví dụ.

7. Số dấu phẩy động được lưu trữ bên trong dưới dạng (mantissa , , số mũ ). Nhật ký tự nhiên của ít tự nhiên hơn nhật ký cơ sở 2, trong đó phần đầu tiên của mã của bạn có thể được thay thế bằng một cuộc gọi đến . m 1x=m×2emex12<m1exfrexp

8. So sánh của bạn logvới logtrong libmhoặc openlibm(ví dụ: https://github.com/JuliaLang/openlibm/blob/master/src/e_log.c ). Đây là cách dễ nhất để tìm hiểu những gì người khác đã tìm ra. Ngoài ra còn có các phiên bản được tối ưu hóa libm đặc biệt dành riêng cho các nhà sản xuất CPU, nhưng những phiên bản thường không được công bố mã nguồn.

Boost :: sf có một số chức năng đặc biệt, nhưng không phải là những chức năng cơ bản. Tuy nhiên, có thể nên xem nguồn log1p: http://www.boost.org/doc/libs/1_58_0/libs/math/doc/html/math_toolkit/powers/log1p.html

Ngoài ra còn có các thư viện số học chính xác tùy ý mã nguồn mở như mpfr, có thể sử dụng các thuật toán khác nhau so với libm do yêu cầu độ chính xác cao hơn.

9. Độ chính xác và tính ổn định của thuật toán số của Higham là phần giới thiệu cấp trên tốt để phân tích lỗi của thuật toán số. Đối với bản thân các thuật toán gần đúng, Thực hành xấp xỉ lý thuyết gần đúng của Trefethen là một tài liệu tham khảo tốt.

10. Tôi biết điều này được nói hơi quá thường xuyên, nhưng các dự án phần mềm lớn hợp lý hiếm khi phụ thuộc vào thời gian chạy của một chức năng nhỏ được gọi đi gọi lại. Nó không quá phổ biến để phải lo lắng về hiệu suất của nhật ký, trừ khi bạn định hình chương trình của bạn và đảm bảo rằng nó quan trọng.


Cảm ơn bạn đã trả lời kỹ lưỡng, nó đã giúp tôi với một số vấn đề rất cấp bách. Đầu tiên, tôi không phải đối phó với số lượng rất lớn, thông thường tôi chỉ phải làm việc với các số nguyên, do đó các số trong phạm vi là khá tốt đối với tôi. Và loạt arctanh đã hoạt động tốt hơn đối với tôi về tốc độ, và nó có khả năng hội tụ tốt hơn so với Taylor. Tôi đã cập nhật các trường hợp góc cho số âm và số không. Ở trạng thái hiện tại, việc triển khai hiển thị lỗi tối đa khoảng , nhưng miễn là tôi không sử dụng tính toán xếp tầng, lỗi không phải là vấn đề lớn. 26414e15
sarker306

Đối với đầu vào lớn nhất có thể, tức là 1.7976e + 308 và 1.7976e-308, việc triển khai hiện tại cho thấy lỗi tuyệt đối là , có thể cần một số công việc để sửa. Để chống lại tràn do nhân lặp lại, một điều kiện có thể được áp dụng, khi biến vòng lặp khoảng 700, chúng ta có thể chia n một lần và tiếp tục lại. (Điều đó có thể giải thích tại sao giá trị lỗi cao hơn một chút ở đây). 1.13e13term
sarker306 14/07/2015

Vì việc mở rộng chuỗi arctanh dường như được hội tụ trong trung bình 13 lần lặp khi được thử nghiệm với các số nguyên , có lẽ chúng ta có thể thực hiện với 20 lần lặp hoặc hơn. Sẽ thật tuyệt nếu chúng ta có một quy tắc lặp lại dựa trên giá trị số của đầu vào. Và đối với đầu vào lớn hơn, vòng lặp đầu tiên sẽ bắt đầu thống trị. Lặp đi lặp lại bình phương có thể giúp đỡ, tôi sẽ xem xét nó. Và cảm ơn cho liên kết libm. Tôi đang nhìn vào nó.  1e8
sarker306

1
@ sarker306 Tôi đã cố gắng đánh giá và ba phiên bản của bạn chậm hơn nhật ký libm của tôi theo các yếu tố 19.4, 9.4, 8.3. k=11071lnk
Kirill

2
@ sarker306 Bạn có thể loại bỏ hoàn toàn vòng lặp đầu tiên bằng cách sử dụng frexpđể lấy số mũ và số mũ của số , sao cho . x=m×2elnx=eln2+lnm
Kirill

5

Câu trả lời của Kirill đã chạm vào một số lượng lớn các vấn đề có liên quan. Tôi muốn mở rộng một số trong số họ dựa trên kinh nghiệm thiết kế thư viện toán học thực tế. Lưu ý trước: các nhà thiết kế thư viện toán học có xu hướng sử dụng mọi tối ưu hóa thuật toán được xuất bản, cũng như nhiều tối ưu hóa cụ thể của máy, không phải tất cả trong số đó sẽ được xuất bản. Mã thường xuyên sẽ được viết bằng ngôn ngữ lắp ráp, thay vì sử dụng mã được biên dịch. Do đó, không chắc rằng việc triển khai đơn giản và được biên dịch sẽ đạt được hơn 75% hiệu suất của việc triển khai thư viện toán chất lượng cao hiện có, giả sử các bộ tính năng giống hệt nhau (độ chính xác, xử lý trường hợp đặc biệt, báo cáo lỗi, hỗ trợ chế độ làm tròn).

Về độ chính xác, đối với các hàm cơ bản (ví dụ: , ) cũng như các hàm đặc biệt đơn giản (ví dụ , ), lỗi ulp đã thay thế lỗi tương đối như số liệu lỗi liên quan. Mục tiêu thiết kế chung cho các chức năng cơ bản là lỗi tối đa dưới 1 ulp, dẫn đến chức năng được làm tròn một cách trung thực. Hàm được làm tròn trung thực trả về số dấu phẩy động gần nhất với kết quả chính xác vô hạn hoặc số dấu phẩy động liền kề ngay lập tức.explogerfcΓ

Độ chính xác thường được đánh giá bằng cách so sánh với tham chiếu có độ chính xác cao hơn (bên thứ ba). Các hàm đối số đơn có độ chính xác đơn có thể dễ dàng được kiểm tra toàn diện, các hàm khác yêu cầu thử nghiệm với các vectơ kiểm tra ngẫu nhiên (có hướng). Rõ ràng người ta không thể tính được kết quả tham chiếu chính xác vô hạn, nhưng nghiên cứu về Dilemma của Table-Maker cho thấy rằng đối với nhiều hàm đơn giản, nó đủ để tính toán một tham chiếu với độ chính xác gấp ba lần độ chính xác của mục tiêu. Xem ví dụ:

Vincent Lefèvre, Jean-Michel Muller, "Những trường hợp xấu nhất đối với việc làm tròn chính xác các chức năng cơ bản trong độ chính xác kép". Trong Kỷ yếu Hội thảo lần thứ 15 về Số học máy tính , 2001.111-118). (in trực tuyến)

Về hiệu suất, người ta phải phân biệt giữa tối ưu hóa độ trễ (quan trọng khi xem xét thời gian thực hiện của các hoạt động phụ thuộc), so với tối ưu hóa thông lượng (có liên quan khi xem xét thời gian thực hiện các hoạt động độc lập). Trong hai mươi năm qua, sự phổ biến của các kỹ thuật song song hóa phần cứng như song song mức lệnh (ví dụ: siêu khối, bộ xử lý không theo thứ tự), song song mức dữ liệu (ví dụ: hướng dẫn SIMD) và song song mức luồng (ví dụ siêu phân luồng, bộ xử lý đa lõi) đã dẫn đến sự nhấn mạnh vào thông lượng tính toán như là số liệu phù hợp hơn.

Các xấp xỉ cốt lõi cho các chức năng cơ bản hầu như chỉ là xấp xỉ minimax . Do chi phí phân chia tương đối cao, xấp xỉ cực đại đa thức. Các công cụ như Mathicala hoặc Maple có các phương tiện tích hợp để tạo ra các công cụ này; cũng có những công cụ chuyên dụng như Sollya . Đối với logarit, các lựa chọn xấp xỉ lõi cơ bản, sau khi giảm đối số thành các giá trị gần với thống nhất, là và , trong đó là một xấp xỉ minimax đa thức. Hiệu suất-khôn ngoan, trước đây thường được ưa thích cho các triển khai chính xác đơn (xem câu trả lời nàyl o g ( x ) = 2 a t a n h ( ( x - 1 ) / ( x + 1 ) ) = p ( ( ( x - 1 ) / ( x + 1 ) ) 2 ) plog(1+x)=p(x)log(x)=2atanh((x1)/(x+1))=p(((x1)/(x+1))2)p cho một ví dụ hoạt động), trong khi cái sau được ưu tiên cho việc triển khai độ chính xác kép.

Hoạt động đa cộng gộp ( FMA ), được IBM giới thiệu lần đầu tiên cách đây 25 năm và hiện có sẵn trên tất cả các kiến ​​trúc bộ xử lý chính, là một khối xây dựng quan trọng của việc triển khai thư viện toán học hiện đại. Nó cung cấp giảm lỗi làm tròn, bảo vệ hạn chế chống lại phép trừ trừ và đơn giản hóa rất nhiều số học hai lần .

Việc C99triển khai độ chính xác kép của IEEE-754 mẫu mực log()dưới đây cho thấy việc sử dụng FMA (được trình bày C99dưới dạng fma()hàm toán học tiêu chuẩn), cùng với việc sử dụng rất hạn chế các kỹ thuật nhân đôi để cải thiện độ chính xác của sản phẩm với hằng số siêu việt. Hai xấp xỉ lõi khác nhau được cung cấp, cả hai đều cung cấp kết quả được làm tròn một cách trung thực, như được chứng minh bằng thử nghiệm với vectơ thử nghiệm ngẫu nhiên. Các xấp xỉ minimax được sử dụng được tính toán với các công cụ của riêng tôi dựa trên thuật toán Remez .233

#include <math.h>

/* compute natural logarithm

   USE_ATANH == 1: maximum error found: 0.83482 ulp @ 0.7012829191167614
   USE_ATANH == 0: maximum error found: 0.83839 ulp @ 1.2788954397331760
*/
double my_log (double a)
{
    const double LOG2_HI = 0x1.62e42fefa39efp-01; // 6.9314718055994529e-01
    const double LOG2_LO = 0x1.abc9e3b39803fp-56; // 2.3190468138462996e-17
    double m, r, i, s, t, p, f, q;
    int e;

    m = frexp (a, &e);
    if (m < 0.70703125) { // 181/256
        m = m + m;
        e = e - 1;
    }
    i = (double)e;

    /* m in [181/256, 362/256] */

#if USE_ATANH
    /* Compute q = (m-1) / (m+1) */
    p = m + 1.0;
    m = m - 1.0;
    q = m / p;

    /* Compute (2*atanh(q)/q-2*q) as p(q**2), q in [-75/437, 53/309] */
    s = q * q;
    r =             0x1.2f1da230fb057p-3;  // 1.4800574027992994e-1
    r = fma (r, s,  0x1.399f73f934c01p-3); // 1.5313616375223663e-1
    r = fma (r, s,  0x1.7466542530accp-3); // 1.8183580149169243e-1
    r = fma (r, s,  0x1.c71c51a8bf129p-3); // 2.2222198291991305e-1
    r = fma (r, s,  0x1.249249425f140p-2); // 2.8571428744887228e-1
    r = fma (r, s,  0x1.999999997f6abp-2); // 3.9999999999404662e-1
    r = fma (r, s,  0x1.5555555555593p-1); // 6.6666666666667351e-1
    r = r * s;

    /* log(a) = 2*atanh(q) + i*log(2) = LOG2_LO*i + p(q**2)*q + 2q + LOG2_HI*i.
       Use K.C. Ng's trick to improve the accuracy of the computation, like so:
       p(q**2)*q + 2q = p(q**2)*q + q*t - t + m, where t = m**2/2.
    */
    t = m * m * 0.5;
    r = fma (q, t, fma (q, r, LOG2_LO * i)) - t + m;
    r = fma (LOG2_HI, i, r);

#else // USE_ATANH

    /* Compute f = m -1 */
    f = m - 1.0;
    s = f * f;

    /* Approximate log1p (f), f in [-75/256, 106/256] */
    r = fma (-0x1.961d64ddd82b6p-6, f, 0x1.d35fd598b1362p-5); // -2.4787281515616676e-2, 5.7052533321928292e-2
    t = fma (-0x1.fcf5138885121p-5, f, 0x1.b97114751d726p-5); // -6.2128580237329929e-2, 5.3886928516403906e-2
    r = fma (r, s, t);
    r = fma (r, f, -0x1.b5b505410388dp-5); // -5.3431043874398211e-2
    r = fma (r, f,  0x1.dd660c0bd22dap-5); //  5.8276198890387668e-2
    r = fma (r, f, -0x1.00bda5ecdad6fp-4); // -6.2680862565391612e-2
    r = fma (r, f,  0x1.1159b2e3bd0dap-4); //  6.6735934054864471e-2
    r = fma (r, f, -0x1.2489f14dd8883p-4); // -7.1420614809115476e-2
    r = fma (r, f,  0x1.3b0ee248a0ccfp-4); //  7.6918491287915489e-2
    r = fma (r, f, -0x1.55557d3b497c3p-4); // -8.3333481965921982e-2
    r = fma (r, f,  0x1.745d4666f7f48p-4); //  9.0909266480136641e-2
    r = fma (r, f, -0x1.999999d959743p-4); // -1.0000000092767629e-1
    r = fma (r, f,  0x1.c71c70bbce7c2p-4); //  1.1111110722131826e-1
    r = fma (r, f, -0x1.fffffffa61619p-4); // -1.2499999991822398e-1
    r = fma (r, f,  0x1.249249262c6cdp-3); //  1.4285714290377030e-1
    r = fma (r, f, -0x1.555555555f03cp-3); // -1.6666666666776730e-1
    r = fma (r, f,  0x1.999999999759ep-3); //  1.9999999999974433e-1
    r = fma (r, f, -0x1.fffffffffff53p-3); // -2.4999999999999520e-1
    r = fma (r, f,  0x1.555555555555dp-2); //  3.3333333333333376e-1
    r = fma (r, f, -0x1.0000000000000p-1); // -5.0000000000000000e-1

    /* log(a) = log1p (f) + i * log(2) */
    p = fma ( LOG2_HI, i, f);
    t = fma (-LOG2_HI, i, p);
    f = fma ( LOG2_LO, i, f - t);
    r = fma (r, s, f);
    r = r + p;
#endif // USE_ATANH

    /* Handle special cases */
    if (!((a > 0.0) && (a <= 0x1.fffffffffffffp1023))) {
        r = a + a;  // handle inputs of NaN, +Inf
        if (a  < 0.0) r =  0.0 / 0.0; //  NaN
        if (a == 0.0) r = -1.0 / 0.0; // -Inf
    }
    return r;
}

(+1) Bạn có biết các triển khai nguồn mở phổ biến (như openlibm) có tốt như có thể không, hoặc các chức năng đặc biệt của chúng có thể được cải thiện không?
Kirill

1
@Kirill Cuối cùng tôi đã xem các triển khai nguồn mở (nhiều năm trước), họ không khai thác lợi ích của FMA. Vào thời điểm đó, IBM Power và Intel Itanium là những kiến ​​trúc duy nhất bao gồm hoạt động, bây giờ hỗ trợ phần cứng cho nó rất phổ biến. Ngoài ra, các phép tính xấp xỉ đa thức cộng với bảng là trạng thái của nghệ thuật trước đây, bây giờ các bảng không được ưa chuộng: truy cập bộ nhớ dẫn đến việc sử dụng năng lượng cao hơn, chúng có thể (và làm) can thiệp vào vector hóa và thông lượng tính toán tăng nhiều hơn thông lượng bộ nhớ dẫn đến tác động tiêu cực tiềm năng từ các bảng.
njuffa
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.