Який алгоритм є більш точним для обчислення суми відсортованого масиву чисел?


22

Беручи під увагу зростаючої кінцевої послідовності позитивних чисел . Який із наведених нижче алгоритмів краще для обчислення суми чисел?z1,z2,.....zn

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

Або:

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

На мою думку, було б краще почати додавати числа від найбільшого до найменшого числа, оскільки помилка стає все меншою та меншою. Ми також знаємо, що коли ми додаємо дуже велике число до дуже малого числа, приблизним результатом може бути велике число.

Це правильно? Що ще можна сказати?

Відповіді:


18

Додавання довільних чисел з плаваючою точкою зазвичай дасть деяку помилку округлення, а помилка округлення буде пропорційною розміру результату. Якщо ви обчислите одну суму і почнете, додавши спочатку найбільші числа, середній результат буде більшим. Так ви б почали додавати з найменших цифр.

Але ви отримуєте кращий результат (і він працює швидше), якщо ви отримаєте чотири суми, наприклад: Почніть з sum1, sum2, sum3, sum4 і додайте по черзі чотири елементи масиву з sum1, sum2, sum3, sum4. Оскільки кожен результат в середньому становить лише 1/4 від початкової суми, ваша помилка в чотири рази менша.

Ще краще: додайте числа парами. Потім додайте результати парами. Знову додайте ці результати парами і так далі, поки вам не залишиться два числа.

Дуже просто: використовуйте більш високу точність. Використовуйте довгий подвійний, щоб обчислити суму пар. Використовуйте подвійний для обчислення суми плавців.

Близько до ідеального: Знайдіть алгоритм Кахана, описаний раніше. Найкраще все-таки використовується, додаючи починаючи з найменшого числа.


26

Це цілі числа чи числа з плаваючою комою? Припускаючи, що це плаваюча точка, я б пішов із першим варіантом. Краще додати менші числа один до одного, а потім додати більші числа пізніше. З другим варіантом ви в кінцевому підсумку додасте невелике число до великої кількості, оскільки я збільшуватиметься, що може призвести до проблем. Ось хороший ресурс з арифметики з плаваючою комою: Що повинен знати кожен комп'ютерний арифметик з плаваючою комою


24

відповідь animal_magic правильна, що вам слід додати числа від найменших до найбільших, проте я хочу навести приклад, щоб показати, чому.

Припустимо, що ми працюємо у форматі з плаваючою комою, який дає нам приголомшливі 3 цифри точності. Тепер ми хочемо додати десять чисел:

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

Звичайно, точна відповідь - 1009, але ми не можемо отримати це у нашому тризначному форматі. Округлення до 3-х цифр, найточніша відповідь, яку ми отримуємо, - це 1010. Якщо додати найменшу до найбільшої, на кожну петлю отримуємо:

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

Таким чином ми отримуємо максимально точну відповідь для нашого формату. Тепер припустимо, що ми додаємо від найбільшого до найменшого.

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

Оскільки номери з плаваючою комою округляються після кожної операції, всі додавання округляються, збільшуючи нашу помилку з 1 до 9 від точної. А тепер уявіть, якби ваш набір чисел додавав 1000, а потім сто 1 або мільйон. Зауважте, що, щоб бути по-справжньому точним, ви хочете підсумувати найменші два числа, а потім вдатися до результату у свій набір чисел.


15

У загальному випадку я б використовував компенсовану підсумовуваність (або підсумовування Кахана). Якщо цифри вже не відсортовані, сортування їх буде набагато дорожче, ніж їх додавання . Компенсоване підсумовування також є більш точним, ніж сортоване підсумовування або наївне підсумовування (див. Попереднє посилання).

Щодо посилань, що повинен знати кожен програміст про арифметику з плаваючою комою охоплює основні точки досить детально, щоб хтось міг прочитати його за 20 (+/- 10) хвилин і зрозуміти основи. "Те, що повинен знати кожен науковець про арифметику з плаваючою комою" Голдберга - класична довідка, але більшість людей, яких я знаю, рекомендують, щоб папір не читав її детально, тому що це близько 50 сторінок (більше, ніж у деяких друковані видання) та написані у щільній прозі, тому я маю труднощі рекомендувати це як першочергове посилання для людей. Це добре для другого погляду на тему. Енциклопедична довідка - це точність та стабільність чисельних алгоритмів Хігема, який охоплює цей матеріал, а також накопичення числових помилок у багатьох інших алгоритмах; це також 680 сторінок, тож я б і не звертався до цього посилання спочатку.


2
Для повноти у книзі Хігхема ви знайдете відповідь на оригінальне запитання на сторінці 82 : все більшу замовлення найкраще. Також є Розділ (4.6), який обговорює вибір методу.
Федеріко Полоні

7

Попередні відповіді вже обговорювали це питання в цілому і дають слушну пораду, але є додаткова примха, яку я хотів би зазначити. У більшості сучасних архітектур forцикл, який ви описали, все одно буде виконуватися з 80-бітовою розширеною точністю , що гарантує додаткову точність, оскільки всі тимчасові змінні будуть введені в регістри. Отже, у вас вже є певна форма захисту від числових помилок. Однак у складніших циклах проміжні значення зберігатимуться в пам'яті між операціями, а отже, скорочуються до 64 біт. Я гадаю, що

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

достатньо, щоб отримати меншу точність у підсумовуванні (!!). Тому будьте дуже обережні, якщо хочете роздрукувати код-налагодження свого коду, перевіряючи на точність.

Для зацікавлених у цій роботі описана проблема в широко використовуваній числовій процедурі (QR-факторизація Лапака, що розкриває рейтинг), налагодження та аналіз якої були дуже складними саме через цю проблему.


1
Більшість сучасних машин 64-бітні, і вони використовують або SSE, або AVX, навіть для скалярних операцій. Ці блоки не підтримують 80-бітну арифметику і використовують ту саму внутрішню точність, що й аргументи операції. Використання x87 FPU зараз взагалі не рекомендується, і більшість 64-бітних компіляторів потребують спеціальних опцій, щоб змусити його використовувати.
Христо Ілієв

1
@HristoIliev Спасибі за коментар, я цього не знав!
Федеріко Полоні

4

З двох варіантів додавання від менших до більших призведе до меншої числової помилки, а потім додавання від більшої до меншої.

Однак,> 20 років тому в моєму класі "Числові методи" інструктор заявив це, і мені прийшло в голову, що це все-таки вводить більше помилок, ніж потрібно через відносну різницю у величині між акумулятором і значеннями, що додаються.

Логічно кращим рішенням є додати 2 найменших числа у списку, а потім повторно вставити підсумкове значення у відсортований список.

Щоб продемонструвати це, я розробив алгоритм, який міг би зробити це ефективно (у просторі та в часі), використовуючи звільнений простір, як елементи були вилучені з основного масиву, щоб створити вторинний масив підсумованих значень, які за своєю суттю були впорядковані після додавання становили суми значень, які постійно зростали. Після кожної ітерації перевіряються "підказки" обох масивів, щоб знайти 2 найменших значення.


2

Оскільки ви не обмежили тип даних, який буде використовуватися, щоб досягти абсолютно точного результату, просто використовуйте довільні числа довжини ... в такому випадку порядок не має значення. Це буде набагато повільніше, але для досягнення досконалості потрібен час.


0

Використовуйте бінарне додавання дерев, тобто виберіть середнє значення розподілу (найближче число) як корінь двійкового дерева та створіть відсортоване бінарне дерево, додавши менші значення ліворуч від графіка, а більші - праворуч тощо . Рекурсивно додайте всі дочірні вузли одного батька при підході знизу вгору. Це буде ефективно, оскільки похибка середнього рівня збільшуватиметься із кількістю підсумків, а у підходах до двійкового дерева кількість підсумків у порядку log n у базі 2. Отже, помилка avg була б меншою.


Це те саме, що додавати сусідні пари в початковий масив (оскільки він сортується). Немає жодної причини заносити всі значення в дерево.
Годрік Провид

0

Те, що Христо Ілієв говорив вище про 64-розрядних компіляторах, що віддають перевагу інструкціям SSE та AVX над FPU (AKA NDP), абсолютно вірно, принаймні для Microsoft Visual Studio 2013. Однак для подвійної точності операцій з плаваючою комою я використовував фактично швидше, як і теоретично точніше, використовувати ФПУ. Якщо для вас це важливо, я б запропонував спершу протестувати різні рішення, перш ніж вибрати остаточний підхід.

Під час роботи в Java я дуже часто використовую тип даних BigDecimal довільної точності. Це занадто просто, і зазвичай не помічає зниження швидкості. Обчислення трансцендентальних функцій з нескінченними рядами та sqrt методом Ньютона може зайняти мілісекунд або більше, але це можливо і досить точно.


0

Я залишив це лише тут /programming//a/58006104/860099 (коли ви заходите туди, натисніть "Показати фрагмент коду" та запустіть його кнопкою

Це приклад JavaScript, який чітко показує, що сума, починаючи з найбільшої, призводить до більшої помилки

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);

Відповіді, що містять лише посилання, не рекомендують на цьому веб-сайті. Чи можете ви пояснити, що передбачено у посиланні?
nicoguaro

@nicoguaro Я оновлюю відповідь - всі відповіді дуже приємні, але ось конкретний приклад
Kamil Kiełczewski
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.