Thuật toán nào chính xác hơn để tính tổng của một dãy số được sắp xếp?


22

Cho là một chuỗi hữu hạn tăng các số dương . Trong hai thuật toán sau đây là tốt hơn để tính tổng của các số?z1,z2,.....zn

s=0; 
for \ i=1:n 
    s=s + z_{i} ; 
end

Hoặc là:

s=0; 
for \ i=1:n 
s=s + z_{n-i+1} ; 
end

Theo tôi, tốt hơn là bắt đầu thêm các số từ số lớn nhất đến số nhỏ nhất, vì lỗi càng ngày càng nhỏ. Chúng ta cũng biết rằng khi chúng ta thêm một số rất lớn vào một số rất nhỏ, kết quả gần đúng có thể là số lớn.

Điều này có đúng không? Những gì khác có thể được nói?

Câu trả lời:


18

Việc thêm các số dấu phẩy động tùy ý thường sẽ gây ra một số lỗi làm tròn và lỗi làm tròn sẽ tỷ lệ thuận với kích thước của kết quả. Nếu bạn tính một số tiền duy nhất và bắt đầu bằng cách thêm các số lớn nhất trước, kết quả trung bình sẽ lớn hơn. Vì vậy, bạn sẽ bắt đầu thêm với những con số nhỏ nhất.

Nhưng bạn sẽ có kết quả tốt hơn (và nó chạy nhanh hơn) nếu bạn tạo ra bốn khoản tiền, ví dụ: Bắt đầu với sum1, sum2, sum3, sum4 và lần lượt thêm bốn phần tử mảng vào sum1, sum2, sum3, sum4. Vì mỗi kết quả trung bình chỉ bằng 1/4 so với tổng ban đầu, lỗi của bạn nhỏ hơn bốn lần.

Vẫn còn tốt hơn: Thêm các số theo cặp. Sau đó thêm kết quả theo cặp. Thêm các kết quả đó theo cặp một lần nữa, và cứ như vậy cho đến khi bạn còn lại hai số để thêm.

Rất đơn giản: Sử dụng độ chính xác cao hơn. Sử dụng gấp đôi dài để tính tổng số gấp đôi. Sử dụng gấp đôi để tính tổng số phao.

Gần hoàn hảo: Tra cứu thuật toán của Kahan, được mô tả trước đây. Tốt nhất vẫn được sử dụng bằng cách thêm bắt đầu với số lượng nhỏ nhất.


26

Là các số nguyên hoặc số dấu phẩy động? Giả sử đó là điểm nổi, tôi sẽ chọn tùy chọn đầu tiên. Tốt hơn là thêm các số nhỏ hơn với nhau, sau đó thêm các số lớn hơn sau đó. Với tùy chọn thứ hai, cuối cùng bạn sẽ thêm một số nhỏ vào một số lớn khi tôi tăng, điều này có thể dẫn đến các vấn đề. Đây là một tài nguyên tốt về số học dấu phẩy động: Điều mà mọi nhà khoa học máy tính nên biết về số học dấu phẩy động


24

Câu trả lời của Animal_magic là chính xác rằng bạn nên thêm các số từ nhỏ nhất đến lớn nhất, tuy nhiên tôi muốn đưa ra một ví dụ để cho thấy tại sao.

Giả sử chúng ta đang làm việc trong một định dạng dấu phẩy động cung cấp cho chúng ta 3 chữ số chính xác đáng kinh ngạc. Bây giờ chúng tôi muốn thêm mười số:

[1000, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Tất nhiên câu trả lời chính xác là 1009, nhưng chúng ta không thể có được điều đó ở định dạng 3 chữ số. Làm tròn đến 3 chữ số, câu trả lời chính xác nhất chúng tôi nhận được là 1010. Nếu chúng tôi thêm nhỏ nhất đến lớn nhất, trên mỗi vòng lặp, chúng tôi nhận được:

Loop Index        s
1                 1
2                 2
3                 3
4                 4
5                 5
6                 6
7                 7
8                 8
9                 9
10                1009 -> 1010

Vì vậy, chúng tôi nhận được câu trả lời chính xác nhất có thể cho định dạng của chúng tôi. Bây giờ hãy giả sử rằng chúng ta thêm từ lớn nhất đến nhỏ nhất.

Loop Index        s
1                 1000
2                 1001 -> 1000
3                 1001 -> 1000
4                 1001 -> 1000
5                 1001 -> 1000
6                 1001 -> 1000
7                 1001 -> 1000
8                 1001 -> 1000
9                 1001 -> 1000
10                1001 -> 1000

Vì các số dấu phẩy động được làm tròn sau mỗi thao tác, tất cả các bổ sung được làm tròn đi, làm tăng lỗi của chúng tôi từ 1 đến 9 từ chính xác. Bây giờ hãy tưởng tượng nếu bộ số cần thêm của bạn có 1000, và sau đó là một trăm, hoặc một triệu. Lưu ý rằng để thực sự chính xác, bạn sẽ muốn tính tổng hai số nhỏ nhất, sau đó sử dụng kết quả vào tập hợp số của bạn.


15

Đối với trường hợp chung, tôi sẽ sử dụng tổng cộng có bù (hoặc tổng cộng Kahan). Trừ khi các số đã được sắp xếp, việc sắp xếp chúng sẽ tốn kém hơn nhiều so với việc thêm chúng . Tổng kết bù cũng chính xác hơn so với tổng cộng được sắp xếp hoặc tổng kết ngây thơ (xem liên kết trước).

Đối với tài liệu tham khảo, Điều mà mọi lập trình viên nên biết về số học dấu phẩy động bao gồm các điểm cơ bản đủ chi tiết để ai đó có thể đọc nó trong 20 (+/- 10) phút và hiểu những điều cơ bản. "Điều mà mọi nhà khoa học máy tính nên biết về số học dấu phẩy động" của Goldberg là tài liệu tham khảo cổ điển, nhưng hầu hết mọi người tôi biết rằng họ không đọc chi tiết về nó, bởi vì nó có khoảng 50 trang (nhiều hơn thế, trong một số bản in), và được viết bằng văn xuôi dày đặc, vì vậy tôi gặp khó khăn khi giới thiệu đó là tài liệu tham khảo đầu tiên cho mọi người. Nó là tốt cho một cái nhìn thứ hai về chủ đề này. Một tài liệu tham khảo bách khoa là Độ chính xác và tính ổn định của thuật toán số, bao gồm tài liệu này, cũng như sự tích lũy của các lỗi số trong nhiều thuật toán khác; nó cũng có 680 trang, vì vậy tôi cũng sẽ không xem tài liệu tham khảo này trước.


2
Để đầy đủ, trong cuốn sách của Higham, bạn sẽ tìm thấy câu trả lời cho câu hỏi ban đầu ở trang 82 : thứ tự tăng dần là thứ tốt nhất. Ngoài ra còn có Phần (4.6) thảo luận về việc lựa chọn phương pháp.
Federico Poloni

7

Các câu trả lời trước đã thảo luận về vấn đề này và đưa ra lời khuyên hợp lý, nhưng có một câu hỏi bổ sung mà tôi muốn đề cập. Trên hầu hết các kiến ​​trúc hiện đại, forvòng lặp mà bạn đã mô tả sẽ được thực hiện bằng mọi cách với độ chính xác mở rộng 80 bit , đảm bảo độ chính xác bổ sung, vì tất cả các biến tạm thời sẽ được đưa vào các thanh ghi. Vì vậy, bạn đã có một số hình thức bảo vệ khỏi các lỗi số. Tuy nhiên, trong các vòng lặp phức tạp hơn, các giá trị trung gian sẽ được lưu trữ trong bộ nhớ ở giữa các hoạt động và do đó bị cắt ngắn thành 64 bit. Tôi đoán rằng

s=0; 
for \ i=1:n 
    printf("Hello World");
    s=s + z_{i} ; 
end

đủ để có được độ chính xác thấp hơn trong tổng kết của bạn (!!). Vì vậy, hãy thật cẩn thận nếu bạn muốn printf-debug mã của bạn trong khi kiểm tra tính chính xác.

Đối với người quan tâm, bài viết này mô tả một vấn đề trong một thói quen số được sử dụng rộng rãi (nhân tố QR tiết lộ thứ hạng của Lapack) mà việc gỡ lỗi và phân tích chính xác là rất khó khăn vì vấn đề này.


1
Hầu hết các máy hiện đại là 64 bit và chúng sử dụng các đơn vị SSE hoặc AVX ngay cả cho các hoạt động vô hướng. Các đơn vị đó không hỗ trợ số học 80 bit và sử dụng độ chính xác bên trong giống như các đối số hoạt động. Hiện tại, việc sử dụng x87 FPU thường không được khuyến khích và hầu hết các trình biên dịch 64 bit cần các tùy chọn đặc biệt để buộc phải sử dụng nó.
Hristo Iliev

1
@HristoIliev Cảm ơn bạn đã bình luận, tôi không biết điều này!
Federico Poloni

4

Trong số 2 tùy chọn, việc thêm từ nhỏ hơn đến lớn hơn sẽ tạo ra ít lỗi số hơn sau đó thêm từ lớn hơn đến nhỏ hơn.

Tuy nhiên,> 20 năm trước trong lớp "Phương pháp số" của tôi, người hướng dẫn đã nói điều này và với tôi rằng điều này vẫn còn gây ra nhiều lỗi hơn mức cần thiết vì sự khác biệt tương đối về giá trị giữa bộ tích lũy và giá trị được thêm vào.

Theo logic, một giải pháp thích hợp hơn là thêm 2 số nhỏ nhất vào danh sách, sau đó chèn lại giá trị tổng vào danh sách đã sắp xếp.

Để chứng minh điều đó, tôi đã tìm ra một thuật toán có thể thực hiện điều đó một cách hiệu quả (trong không gian và thời gian) bằng cách sử dụng không gian được giải phóng khi các phần tử được loại bỏ khỏi mảng chính để xây dựng một mảng thứ cấp của các giá trị tổng hợp vốn đã được đặt hàng kể từ khi bổ sung là tổng của các giá trị luôn luôn tăng. Trên mỗi lần lặp, "mẹo" của cả hai mảng sau đó được kiểm tra để tìm 2 giá trị nhỏ nhất.


2

Vì bạn đã không hạn chế loại dữ liệu được sử dụng, để đạt được kết quả hoàn toàn chính xác, chỉ cần sử dụng các số có độ dài tùy ý ... trong trường hợp đó, thứ tự sẽ không thành vấn đề. Nó sẽ chậm hơn nhiều, nhưng để có được sự hoàn hảo cần có thời gian.


0

Sử dụng bổ sung cây nhị phân, nghĩa là Chọn giá trị trung bình của phân phối (số gần nhất) làm gốc của cây nhị phân và tạo cây nhị phân được sắp xếp bằng cách thêm các giá trị nhỏ hơn vào bên trái của biểu đồ và các giá trị lớn hơn ở bên phải, v.v. . Việc thêm tất cả các nút con của một cha mẹ đệ quy theo cách tiếp cận từ dưới lên. Điều này sẽ hiệu quả khi lỗi avg tăng theo số lần tính tổng và theo cách tiếp cận cây nhị phân, số lần tóm tắt theo thứ tự log n trong cơ sở 2. Do đó, lỗi avg sẽ ít hơn.


Điều này giống như việc thêm các cặp liền kề trong mảng ban đầu (vì nó được sắp xếp). Không có lý do để đặt tất cả các giá trị vào cây.
Godric Seer

0

Những gì Hristo Iliev đã nói ở trên về trình biên dịch 64 bit thích các hướng dẫn SSE và AVX hơn FPU (AKA NDP) là hoàn toàn đúng, ít nhất là đối với Microsoft Visual Studio 2013. Tuy nhiên, đối với các hoạt động điểm nổi chính xác kép mà tôi đang sử dụng tôi đã tìm thấy nó thực sự nhanh hơn, cũng như trên lý thuyết chính xác hơn, để sử dụng FPU. Nếu nó quan trọng với bạn, tôi sẽ đề nghị thử nghiệm các giải pháp khác nhau trước khi chọn phương pháp cuối cùng.

Khi làm việc trong Java, tôi rất thường xuyên sử dụng kiểu dữ liệu BigDecimal chính xác tùy ý. Nó quá dễ dàng và người ta thường không nhận thấy tốc độ giảm. Việc tính toán các hàm siêu việt với chuỗi vô hạn và sqrt bằng phương pháp Newton có thể mất một phần nghìn giây trở lên, nhưng nó có thể thực hiện được và khá chính xác.


0

Tôi chỉ để lại ở đây /programming//a/58006104/860099 (khi bạn đến đó, nhấp để 'hiển thị đoạn mã' và chạy nó bằng nút

Đó là ví dụ JavaScript cho thấy rõ rằng tổng bắt đầu từ lớn nhất gây ra lỗi lớn hơn

arr=[9,.6,.1,.1,.1,.1];

sum     =             arr.reduce((a,c)=>a+c,0);  // =  9.999999999999998
sortSum = [...arr].sort().reduce((a,c)=>a+c,0);  // = 10

console.log('sum:     ',sum);
console.log('sortSum:',sortSum);

Câu trả lời chỉ liên kết được khuyến khích trong trang web này. Bạn có thể giải thích những gì được cung cấp trong liên kết?
nicoguaro

@nicoguaro Tôi cập nhật câu trả lời - tất cả các câu trả lời đều rất hay, nhưng đây là ví dụ cụ thể
Kamil Kiełczewski
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.