Nên thêm float theo thứ tự nào để có kết quả chính xác nhất?


105

Đây là một câu hỏi tôi đã được hỏi trong cuộc phỏng vấn gần đây và tôi muốn biết (tôi thực sự không nhớ lý thuyết về phân tích số, vì vậy hãy giúp tôi :)

Nếu chúng ta có một số hàm tích lũy các số dấu phẩy động:

std::accumulate(v.begin(), v.end(), 0.0);

vlà một std::vector<float>ví dụ.

  • Sẽ tốt hơn nếu bạn sắp xếp những con số này trước khi cộng dồn chúng?

  • Thứ tự nào sẽ cho câu trả lời chính xác nhất?

Tôi nghi ngờ rằng việc sắp xếp các số theo thứ tự tăng dần sẽ thực sự làm cho lỗi số ít hơn , nhưng tiếc là tôi không thể tự mình chứng minh điều đó.

Tái bút Tôi nhận ra điều này có lẽ không liên quan gì đến lập trình thế giới thực, chỉ là tò mò.


17
Điều này thực sự liên quan đến mọi thứ liên quan đến lập trình trong thế giới thực. Tuy nhiên, nhiều ứng dụng không thực sự CHĂM SÓC về độ chính xác tuyệt đối của phép tính miễn là nó 'khá gần'. Ứng dụng kỹ thuật? Vô cùng quan trọng. Ứng dụng y tế? Vô cùng quan trọng. Thống kê quy mô lớn? Độ chính xác thấp hơn một chút có thể chấp nhận được.
Zéychin

18
Vui lòng không trả lời trừ khi bạn thực sự biết và có thể trỏ đến trang giải thích chi tiết lý do của bạn. Đã có quá nhiều chuyện tào lao về các số dấu phẩy động bay xung quanh mà chúng tôi không muốn thêm vào đó. Nếu bạn nghĩ rằng bạn biết. DỪNG LẠI. bởi vì nếu bạn chỉ nghĩ rằng bạn biết thì có lẽ bạn đã sai.
Martin York

4
@ Zéychin "Ứng dụng kỹ thuật? Cực kỳ quan trọng. Ứng dụng y tế? Cực kỳ quan trọng." ??? Tôi nghĩ rằng bạn sẽ ngạc nhiên nếu bạn biết sự thật :)
BЈовић

3
@Zeychin Lỗi tuyệt đối không liên quan. Điều quan trọng là sai số tương đối. Nếu vài phần trăm của radian là 0,001%, thì ai quan tâm?
BЈовић

3
Tôi thực sự khuyên bạn nên đọc bài này: "những gì mọi nhà khoa học máy tính cần biết về dấu phẩy động" perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf
Mohammad Alaggan 15/02/16

Câu trả lời:


108

Bản năng của bạn về cơ bản là đúng, sắp xếp theo thứ tự tăng dần (về độ lớn) thường cải thiện được phần nào mọi thứ. Hãy xem xét trường hợp chúng ta đang thêm số float chính xác đơn (32 bit) và có 1 tỷ giá trị bằng 1 / (1 tỷ) và một giá trị bằng 1. Nếu giá trị 1 đứng trước thì tổng sẽ đến thành 1, vì 1 + (1/1 tỷ) là 1 do mất độ chính xác. Mỗi phép cộng không ảnh hưởng gì đến tổng số.

Nếu các giá trị nhỏ đến trước, ít nhất chúng sẽ cộng lại thành một cái gì đó, mặc dù ngay cả khi đó tôi có 2 ^ 30 trong số đó, trong khi sau 2 ^ 25 hoặc lâu hơn, tôi quay lại tình huống mỗi giá trị riêng lẻ không ảnh hưởng đến tổng nữa không. Vì vậy, tôi vẫn sẽ cần nhiều thủ thuật hơn.

Đó là một trường hợp cực đoan, nhưng nói chung việc thêm hai giá trị có độ lớn tương tự nhau sẽ chính xác hơn là thêm hai giá trị có độ lớn rất khác nhau, vì bạn "loại bỏ" ít bit độ chính xác hơn ở giá trị nhỏ hơn theo cách đó. Bằng cách sắp xếp các số, bạn nhóm các giá trị có độ lớn tương tự lại với nhau và bằng cách thêm chúng theo thứ tự tăng dần, bạn cho các giá trị nhỏ "cơ hội" tích lũy đạt đến độ lớn của các số lớn hơn.

Tuy nhiên, nếu có liên quan đến các số âm thì rất dễ "qua mặt" cách tiếp cận này. Hãy xem xét ba giá trị để tính tổng {1, -1, 1 billionth},. Tổng đúng về mặt số học là 1 billionth, nhưng nếu phép cộng đầu tiên của tôi liên quan đến giá trị nhỏ thì tổng cuối cùng của tôi sẽ là 0. Trong số 6 lệnh có thể, chỉ có 2 lệnh là "đúng" - {1, -1, 1 billionth}{-1, 1, 1 billionth}. Tất cả 6 lệnh đều cho kết quả chính xác ở thang của giá trị có độ lớn lớn nhất trong đầu vào (0,0000001% ra), nhưng đối với 4 lệnh trong số đó, kết quả không chính xác ở thang của giá trị đúng (100%). Vấn đề cụ thể mà bạn đang giải quyết sẽ cho bạn biết liệu vấn đề cũ có đủ tốt hay không.

Trên thực tế, bạn có thể chơi nhiều thủ thuật hơn là chỉ thêm chúng theo thứ tự đã sắp xếp. Nếu bạn có nhiều giá trị rất nhỏ, một số giá trị trung bình và một số lượng nhỏ các giá trị lớn, thì cách chính xác nhất là cộng tất cả các giá trị nhỏ trước, sau đó cộng riêng các giá trị trung bình, cộng hai tổng đó cùng nhau sau đó thêm những cái lớn. Việc tìm ra sự kết hợp chính xác nhất của các phép cộng dấu phẩy động hoàn toàn không phải là chuyện nhỏ, nhưng để đối phó với những trường hợp thực sự tồi tệ, bạn có thể giữ một loạt các tổng đang chạy ở các độ lớn khác nhau, thêm từng giá trị mới vào tổng phù hợp nhất với độ lớn của nó, và khi tổng số đang chạy bắt đầu quá lớn so với độ lớn của nó, hãy thêm nó vào tổng số tiếp theo và bắt đầu một tổng mới. Được coi là cực đoan logic của nó, quá trình này tương đương với việc thực hiện tổng trong một kiểu chính xác tùy ý (vì vậy bạn ' d làm điều đó). Nhưng với sự lựa chọn đơn giản là thêm vào theo thứ tự cường độ tăng dần hoặc giảm dần, tăng dần là đặt cược tốt hơn.

Nó có một số liên quan đến lập trình trong thế giới thực, vì có một số trường hợp mà phép tính của bạn có thể sai rất nặng nếu bạn vô tình cắt bỏ một đuôi "nặng" bao gồm một số lượng lớn các giá trị mà mỗi giá trị quá nhỏ để ảnh hưởng riêng lẻ tổng, hoặc nếu bạn loại bỏ quá nhiều độ chính xác từ nhiều giá trị nhỏ mà chỉ ảnh hưởng đến một vài bit cuối cùng của tổng. Trong trường hợp đuôi không đáng kể, bạn có thể không quan tâm. Ví dụ: nếu bạn chỉ cộng một số lượng nhỏ các giá trị với nhau ngay từ đầu và bạn chỉ sử dụng một vài số liệu quan trọng của tổng.


8
+1 để giải thích. Điều này hơi phản trực quan vì phép cộng thường ổn định về mặt số học (không giống như phép trừ và phép chia).
Konrad Rudolph

2
@ Konrad, nó có thể là số lượng ổn định, nhưng nó không phải là chính xác cho độ lớn khác nhau của toán hạng :)
MSN

3
@ 6502: chúng được sắp xếp theo thứ tự độ lớn, vì vậy -1 ở cuối. Nếu giá trị thực của tổng có độ lớn là 1, thì tốt thôi. Nếu bạn cộng ba giá trị với nhau: 1 / tỷ, 1 và -1, thì bạn sẽ nhận được 0, tại thời điểm đó bạn phải trả lời câu hỏi thực tế thú vị - bạn có cần câu trả lời chính xác ở quy mô tổng đúng hay bạn chỉ cần một câu trả lời chính xác ở thang giá trị lớn nhất? Đối với một số ứng dụng thực tế, cái sau là đủ tốt, nhưng khi nó không phù hợp, bạn cần một cách tiếp cận phức tạp hơn. Vật lý lượng tử sử dụng tái chuẩn hóa.
Steve Jessop,

8
Nếu bạn định gắn bó với sơ đồ đơn giản này, tôi sẽ luôn thêm hai số có độ lớn thấp nhất và lắp lại tổng trong tập hợp. (Vâng, có lẽ là một loại hợp nhất sẽ làm việc tốt nhất ở đây Bạn có thể sử dụng một phần của mảng chứa các số tóm tắt trước đây như một khu vực làm việc cho các khoản tiền một phần..)
Neil

2
@Kevin Panko: Phiên bản đơn giản là số float có độ chính xác đơn có 24 chữ số nhị phân, lớn nhất trong số đó là bit đặt lớn nhất trong số. Vì vậy, nếu bạn cộng hai số khác nhau về độ lớn hơn 2 ^ 24 với nhau, bạn sẽ bị mất toàn bộ giá trị nhỏ hơn và nếu chúng khác nhau về độ lớn ở một mức độ nhỏ hơn thì bạn sẽ mất một số bit độ chính xác tương ứng của giá trị nhỏ hơn con số.
Steve Jessop,

88

Ngoài ra còn có một thuật toán được thiết kế cho loại hoạt động tích lũy này, được gọi là Kahan Summation , mà bạn có thể nên biết.

Theo Wikipedia,

Các thuật toán tổng Kahan (còn gọi là tổng kết còn bù ) làm giảm đáng kể số lỗi trong tổng thu được bằng cách thêm một chuỗi các số dấu chấm động chính xác hữu hạn, so với cách tiếp cận rõ ràng. Điều này được thực hiện bằng cách giữ một phần bù chạy riêng biệt (một biến để tích lũy các lỗi nhỏ).

Trong mã giả, thuật toán là:

function kahanSum(input)
 var sum = input[1]
 var c = 0.0          //A running compensation for lost low-order bits.
 for i = 2 to input.length
  y = input[i] - c    //So far, so good: c is zero.
  t = sum + y         //Alas, sum is big, y small, so low-order digits of y are lost.
  c = (t - sum) - y   //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y)
  sum = t             //Algebraically, c should always be zero. Beware eagerly optimising compilers!
 next i               //Next time around, the lost low part will be added to y in a fresh attempt.
return sum

3
+1 bổ sung đáng yêu cho chủ đề này. Bất kỳ trình biên dịch nào "háo hức tối ưu hóa" các câu lệnh đó sẽ bị cấm.
Chris A.

1
Đó là một phương pháp đơn giản để tăng gần gấp đôi độ chính xác, bằng cách sử dụng hai biến tổng hợp sumccó độ lớn khác nhau. Nó có thể được mở rộng đến N biến.
MSalters

2
@ChrisA. bạn cũng có thể kiểm soát rõ ràng điều này trên tất cả các trình biên dịch được tính (ví dụ: thông qua -ffast-mathGCC).
Konrad Rudolph,

6
@Konrad Rudolph cảm ơn bạn đã chỉ ra rằng đây là cách tối ưu hóa khả thi với -ffast-math. Điều tôi học được từ cuộc thảo luận này và liên kết này , đó là nếu bạn quan tâm đến độ chính xác của số, bạn có thể nên tránh sử dụng -ffast-mathnhưng điều đó trong nhiều ứng dụng mà bạn có thể bị ràng buộc bởi CPU nhưng không quan tâm đến tính toán số chính xác, (ví dụ: lập trình trò chơi ), -ffast-mathlà hợp lý để sử dụng. Vì vậy, tôi muốn khen ngợi nhận xét mạnh mẽ từ "bị cấm" của tôi.
Chris A.

Sử dụng biến chính xác kép for sum, c, t, ysẽ hữu ích. Bạn cũng cần phải thêm sum -= cvào trước return sum.
G. Cohen

34

Tôi đã thử ví dụ cực đoan trong câu trả lời do Steve Jessop cung cấp.

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    for (long i = 0; i < billion; ++i)
        sum += small;
    std::cout << std::scientific << std::setprecision(1) << big << " + " << billion << " * " << small << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    sum = 0;
    for (long i = 0; i < billion; ++i)
        sum += small;
    sum += big;
    std::cout  << std::scientific << std::setprecision(1) << billion << " * " << small << " + " << big << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

Tôi nhận được kết quả sau:

1.0e+00 + 1000000000 * 1.0e-09 = 2.000000082740371    (difference = 0.000000082740371)
1000000000 * 1.0e-09 + 1.0e+00 = 1.999999992539933    (difference = 0.000000007460067)

Sai số ở dòng thứ nhất lớn hơn dòng thứ hai mười lần.

Nếu tôi thay đổi doubles thành floats trong đoạn mã trên, tôi nhận được:

1.0e+00 + 1000000000 * 1.0e-09 = 1.000000000000000    (difference = 1.000000000000000)
1000000000 * 1.0e-09 + 1.0e+00 = 1.031250000000000    (difference = 0.968750000000000)

Không câu trả lời nào thậm chí gần với 2.0 (nhưng câu trả lời thứ hai gần hơn một chút).

Sử dụng tổng kết Kahan (với doublecác) như Daniel Pryden mô tả:

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    double c = 0.0;
    for (long i = 0; i < billion; ++i) {
        double y = small - c;
        double t = sum + y;
        c = (t - sum) - y;
        sum = t;
    }

    std::cout << "Kahan sum  = " << std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

Tôi nhận được chính xác 2.0:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

Và ngay cả khi tôi thay đổi doubles thành floats trong đoạn mã trên, tôi vẫn nhận được:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

Có vẻ như Kahan là con đường để đi!


Giá trị "lớn" của tôi bằng 1, không phải 1e9. Câu trả lời thứ hai của bạn, được thêm vào theo thứ tự kích thước tăng dần, là chính xác về mặt toán học (1 tỷ, cộng với một tỷ phần tỷ, là 1 tỷ và 1), mặc dù may mắn hơn bất kỳ tính hợp lý chung nào của phương pháp :-) Lưu ý rằng điều doubleđó không tệ mất độ chính xác khi cộng một tỷ phần tỷ với nhau, vì nó có 52 bit quan trọng, trong khi IEEE floatchỉ có 24 và sẽ.
Steve Jessop,

@Steve, lỗi của tôi, xin lỗi. Tôi đã cập nhật mã ví dụ theo ý bạn.
Andrew Stein,

4
Kahan vẫn có độ chính xác hạn chế, nhưng để xây dựng một trường hợp giết người, bạn cần cả tổng chính và bộ tích lũy lỗi cđể chứa các giá trị lớn hơn nhiều so với triệu hồi tiếp theo. Điều này có nghĩa là tổng triệu hồi và nhỏ hơn nhiều so với tổng chính, vì vậy sẽ phải có rất nhiều trong số chúng để cộng lại. Đặc biệt là với doublesố học.
Steve Jessop,

14

Có một lớp thuật toán giải quyết vấn đề chính xác này mà không cần phải sắp xếp hoặc sắp xếp lại dữ liệu .

Nói cách khác, việc tổng kết có thể được thực hiện trong một lần chuyển dữ liệu. Điều này cũng làm cho các thuật toán như vậy có thể áp dụng trong các tình huống mà tập dữ liệu không được biết trước, ví dụ: nếu dữ liệu đến trong thời gian thực và tổng đang chạy cần được duy trì.

Đây là phần tóm tắt của một bài báo gần đây:

Chúng tôi trình bày một thuật toán mới, trực tuyến để tính tổng chính xác một dòng số dấu phẩy động. "Trực tuyến", chúng tôi muốn nói rằng thuật toán chỉ cần xem một đầu vào tại một thời điểm và có thể nhận một luồng đầu vào có độ dài tùy ý của các đầu vào đó trong khi chỉ yêu cầu bộ nhớ không đổi. "Chính xác", chúng tôi muốn nói rằng tổng của mảng bên trong của thuật toán của chúng tôi chính xác bằng tổng của tất cả các đầu vào và kết quả trả về là tổng được làm tròn chính xác. Bằng chứng về tính đúng đắn có giá trị đối với tất cả các đầu vào (bao gồm các số không chuẩn hóa nhưng tràn môđun trung gian) và không phụ thuộc vào số lượng các triệu và số điều kiện của tổng. Thuật toán tiệm cận chỉ cần 5 FLOP cho mỗi lần triệu hồi và do tính song song cấp hướng dẫn chỉ chạy chậm hơn khoảng 2-3 lần so với hiển nhiên, vòng lặp "tổng kết đệ quy thông thường" nhanh nhưng không tốt khi số lượng triệu hồi và lớn hơn 10.000. Do đó, theo hiểu biết của chúng tôi, nó là thuật toán nhanh nhất, chính xác nhất và hiệu quả nhất trong số các thuật toán đã biết. Thật vậy, rất khó để thấy làm thế nào một thuật toán nhanh hơn hoặc một thuật toán yêu cầu ít FLOP hơn đáng kể có thể tồn tại mà không có cải tiến phần cứng. Một ứng dụng cho một số lượng lớn các triệu hồi được cung cấp.

Nguồn: Thuật toán 908: Tổng kết chính xác trực tuyến các luồng dấu chấm động .


1
@Inverse: Vẫn có các thư viện truyền thống xung quanh. Ngoài ra, mua PDF trực tuyến có giá từ $ 5- $ 15 (tùy thuộc vào việc bạn có phải là thành viên ACM hay không). Cuối cùng, DeepDyve dường như đang đề nghị cho mượn bài báo trong 24 giờ với giá 2,99 đô la (nếu bạn là người mới sử dụng DeepDyve, bạn thậm chí có thể nhận nó miễn phí như một phần của bản dùng thử miễn phí của họ): deepdyve.com/lp/acm /…
NPE

2

Dựa trên câu trả lời của Steve về việc sắp xếp các số theo thứ tự tăng dần, tôi sẽ giới thiệu thêm hai ý tưởng:

  1. Quyết định sự khác biệt về số mũ của hai số trên mà bạn có thể quyết định rằng bạn sẽ mất quá nhiều độ chính xác.

  2. Sau đó, cộng các số theo thứ tự cho đến khi số mũ của bộ tích lũy quá lớn so với số tiếp theo, sau đó đặt bộ tích lũy vào hàng đợi tạm thời và bắt đầu bộ tích lũy với số tiếp theo. Tiếp tục cho đến khi bạn hết danh sách ban đầu.

Bạn lặp lại quá trình với hàng đợi tạm thời (đã sắp xếp nó) và với sự khác biệt có thể lớn hơn về số mũ.

Tôi nghĩ rằng điều này sẽ khá chậm nếu bạn phải tính toán số mũ mọi lúc.

Tôi đã xem nhanh một chương trình và kết quả là 1.99903


2

Tôi nghĩ bạn có thể làm tốt hơn việc sắp xếp các con số trước khi tích lũy, bởi vì trong quá trình tích lũy, tích lũy ngày càng lớn hơn. Nếu bạn có một lượng lớn các số tương tự, bạn sẽ bắt đầu mất độ chính xác nhanh chóng. Đây là những gì tôi sẽ đề xuất thay thế:

while the list has multiple elements
    remove the two smallest elements from the list
    add them and put the result back in
the single element in the list is the result

Tất nhiên thuật toán này sẽ hiệu quả nhất với một hàng đợi ưu tiên thay vì một danh sách. Mã C ++:

template <typename Queue>
void reduce(Queue& queue)
{
    typedef typename Queue::value_type vt;
    while (queue.size() > 1)
    {
        vt x = queue.top();
        queue.pop();
        vt y = queue.top();
        queue.pop();
        queue.push(x + y);
    }
}

người lái xe:

#include <iterator>
#include <queue>

template <typename Iterator>
typename std::iterator_traits<Iterator>::value_type
reduce(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type vt;
    std::priority_queue<vt> positive_queue;
    positive_queue.push(0);
    std::priority_queue<vt> negative_queue;
    negative_queue.push(0);
    for (; begin != end; ++begin)
    {
        vt x = *begin;
        if (x < 0)
        {
            negative_queue.push(x);
        }
        else
        {
            positive_queue.push(-x);
        }
    }
    reduce(positive_queue);
    reduce(negative_queue);
    return negative_queue.top() - positive_queue.top();
}

Các số trong hàng đợi là số âm vì topmang lại số lớn nhất , nhưng chúng ta muốn số nhỏ nhất . Tôi có thể đã cung cấp nhiều đối số mẫu hơn cho hàng đợi, nhưng cách tiếp cận này có vẻ đơn giản hơn.


2

Điều này không hoàn toàn trả lời câu hỏi của bạn, nhưng một điều thông minh cần làm là chạy tổng hai lần, một lần với chế độ làm tròn "làm tròn" và một lần với "làm tròn xuống". So sánh hai câu trả lời và bạn biết / kết quả của mình như thế nào / không chính xác và do đó bạn có cần sử dụng chiến lược tổng hợp thông minh hơn không. Thật không may, hầu hết các ngôn ngữ không làm cho việc thay đổi chế độ làm tròn dấu phẩy động dễ dàng như vậy, bởi vì mọi người không biết rằng nó thực sự hữu ích trong các phép tính hàng ngày.

Hãy xem số học Khoảng thời gian nơi bạn thực hiện tất cả các phép toán như thế này, giữ các giá trị cao nhất và thấp nhất khi bạn thực hiện. Nó dẫn đến một số kết quả thú vị và tối ưu hóa.


0

Cách sắp xếp đơn giản nhất để cải thiện độ chính xác là sắp xếp theo giá trị tuyệt đối tăng dần. Điều đó cho phép các giá trị độ lớn nhỏ nhất có cơ hội tích lũy hoặc hủy bỏ trước khi tương tác với các giá trị độ lớn lớn hơn sẽ làm mất độ chính xác.

Điều đó nói rằng, bạn có thể làm tốt hơn bằng cách theo dõi nhiều tổng từng phần không trùng lặp. Đây là bài báo mô tả kỹ thuật và trình bày bằng chứng về độ chính xác: www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps

Thuật toán đó và các cách tiếp cận khác để tính tổng dấu phẩy động chính xác được triển khai bằng Python đơn giản tại: http://code.activestate.com/recipes/393090/ Ít nhất hai trong số đó có thể được chuyển đổi thành C ++.


0

Đối với IEEE 754 độ chính xác đơn hoặc kép hoặc số định dạng đã biết, một giải pháp thay thế khác là sử dụng một mảng số (được truyền bởi người gọi, hoặc trong một lớp cho C ++) được lập chỉ mục bởi số mũ. Khi thêm các số vào mảng, chỉ các số có cùng số mũ được thêm vào (cho đến khi tìm thấy một ô trống và số đó được lưu trữ). Khi một tổng được gọi, mảng được tính tổng từ nhỏ nhất đến lớn nhất để giảm thiểu việc cắt bớt. Ví dụ về độ chính xác đơn:

/* clear array */
void clearsum(float asum[256])
{
size_t i;
    for(i = 0; i < 256; i++)
        asum[i] = 0.f;
}

/* add a number into array */
void addtosum(float f, float asum[256])
{
size_t i;
    while(1){
        /* i = exponent of f */
        i = ((size_t)((*(unsigned int *)&f)>>23))&0xff;
        if(i == 0xff){          /* max exponent, could be overflow */
            asum[i] += f;
            return;
        }
        if(asum[i] == 0.f){     /* if empty slot store f */
            asum[i] = f;
            return;
        }
        f += asum[i];           /* else add slot to f, clear slot */
        asum[i] = 0.f;          /* and continue until empty slot */
    }
}

/* return sum from array */
float returnsum(float asum[256])
{
float sum = 0.f;
size_t i;
    for(i = 0; i < 256; i++)
        sum += asum[i];
    return sum;
}

ví dụ về độ chính xác kép:

/* clear array */
void clearsum(double asum[2048])
{
size_t i;
    for(i = 0; i < 2048; i++)
        asum[i] = 0.;
}

/* add a number into array */
void addtosum(double d, double asum[2048])
{
size_t i;
    while(1){
        /* i = exponent of d */
        i = ((size_t)((*(unsigned long long *)&d)>>52))&0x7ff;
        if(i == 0x7ff){         /* max exponent, could be overflow */
            asum[i] += d;
            return;
        }
        if(asum[i] == 0.){      /* if empty slot store d */
            asum[i] = d;
            return;
        }
        d += asum[i];           /* else add slot to d, clear slot */
        asum[i] = 0.;           /* and continue until empty slot */
    }
}

/* return sum from array */
double returnsum(double asum[2048])
{
double sum = 0.;
size_t i;
    for(i = 0; i < 2048; i++)
        sum += asum[i];
    return sum;
}

Điều này nghe có vẻ giống phương pháp của Malcolm 1971 hoặc hơn thế nữa, biến thể của nó sử dụng số mũ của Demmel và Hida ("Thuật toán 3"). Có một thuật toán khác thực hiện một vòng lặp dựa trên thực hiện giống như của bạn, nhưng tôi không thể tìm thấy nó vào lúc này.
ZachB

@ZachB - khái niệm tương tự như sắp xếp hợp nhất từ ​​dưới lên cho danh sách liên kết , cũng sử dụng một mảng nhỏ, trong đó mảng [i] trỏ đến danh sách với 2 ^ i nút. Tôi không biết chuyện này đã đi bao xa. Trong trường hợp của tôi, đó là sự tự khám phá vào những năm 1970.
rcgldr

-1

Phao của bạn phải được thêm vào với độ chính xác gấp đôi. Điều đó sẽ cung cấp cho bạn độ chính xác bổ sung hơn bất kỳ kỹ thuật nào khác có thể. Để có độ chính xác cao hơn một chút và tốc độ nhanh hơn đáng kể, bạn có thể tạo bốn tổng và cộng chúng lại ở cuối.

Nếu bạn đang thêm số chính xác gấp đôi, hãy sử dụng kép dài cho tổng - tuy nhiên, điều này sẽ chỉ có tác dụng tích cực trong các triển khai mà số kép dài thực sự có độ chính xác cao hơn gấp đôi (thường là x86, PowerPC tùy thuộc vào cài đặt trình biên dịch).


1
“Điều đó sẽ cung cấp cho bạn độ chính xác bổ sung hơn bất kỳ kỹ thuật nào khác có thể” Bạn có nhận ra rằng câu trả lời của bạn đến hơn một năm sau câu trả lời muộn trước đó mô tả cách sử dụng phép tính tổng chính xác không?
Pascal Cuoq

Loại "dài đôi" rất kinh khủng và bạn không nên sử dụng nó.
Jeff

-1

Về việc sắp xếp, có vẻ như đối với tôi rằng nếu bạn muốn hủy thì các số nên được thêm vào theo thứ tự độ lớn giảm dần , không tăng dần. Ví dụ:

((-1 + 1) + 1e-20) sẽ cho 1e-20

nhưng

((1e-20 + 1) - 1) sẽ cho 0

Trong phương trình đầu tiên, hai số lớn bị loại bỏ, trong khi ở phương trình thứ hai, số hạng 1e-20 bị mất khi thêm vào 1, vì không có đủ độ chính xác để giữ lại.

Ngoài ra, tổng kết theo cặp là khá tốt để tổng hợp nhiều số.

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.