Разберитесь с численной нестабильностью суммирования большого массива чисел в плавающей точке: иллюстрируйте на примере почему простой суммирующий цикл даёт ошибки, опишите алгоритм Кахана и другие техники уменьшения ошибок и оцените их стоимость

23 Апр в 16:08
4 +1
0
Ответы
1
Кратко по сути и с примером.
1) Почему простой цикл даёт ошибки
- Модель округления: при сложении в плавающей точке
fl(a+b)=(a+b)(1+δ),∣δ∣≤ϵ,fl(a+b)=(a+b)(1+\delta),\qquad |\delta|\le\epsilon,fl(a+b)=(a+b)(1+δ),δϵ, где ϵ\epsilonϵ — машинный эпсилон (для IEEE‑754 double ϵ≈2−53≈1.11⋅10−16\epsilon\approx2^{-53}\approx1.11\cdot10^{-16}ϵ2531.111016).
- Накопление ошибок: при наивном суммировании слева направо погрешность растёт примерно как O(nϵ)O(n\epsilon)O(nϵ). Формально для вычисленной суммы s^n\hat s_ns^n и истинной s=∑i=1nxis=\sum_{i=1}^n x_is=i=1n xi справедлива оценка
∣s^n−s∣≤γn−1∑i=1n∣xi∣,γm=mϵ1−mϵ.\;|\hat s_n-s|\le\gamma_{n-1}\sum_{i=1}^n|x_i|,\qquad
\gamma_m=\frac{m\epsilon}{1-m\epsilon}.
s^n sγn1 i=1n xi ,γm =1mϵmϵ .

Простой иллюстративный пример (double): числа [1, 10−16, −1][1,\;10^{-16},\;-1][1,1016,1]. Истинная сумма 1+10−16−1=10−161+10^{-16}-1=10^{-16}1+10161=1016. Наивно слева направо:
- 1+10−161+10^{-16}1+1016 округляется к 111 (потеря малого слагаемого), затем 1−1=01-1=011=0. Результат 000 — потеря информации.
Kahan‑компенсация (см. ниже) восстановит значение близкое к 10−1610^{-16}1016.
2) Алгоритм Кахана (Kahan compensated summation)
Псевдокод (s — накопитель, c — компенсатор, инициализировать s=0, c=0s=0,\;c=0s=0,c=0):
for each xxx do
y=x−c\;y = x - cy=xc
t=s+y\;t = s + yt=s+y
c=(t−s)−y\;c = (t - s) - yc=(ts)y
s=t\;s = ts=t end
Математически:
y=xi−c,t=s+y,c=(t−s)−y,s=t.\;y=x_i-c,\qquad t=s+y,\qquad c=(t-s)-y,\qquad s=t.y=xi c,t=s+y,c=(ts)y,s=t.
Пояснение: ccc аккумулирует потерянные при предыдущих сложениях мелкие биты; при следующем шаге они вычитаются из входа и корректируют сумму.
Эффект и стоимость:
- Значительно уменьшает накопление погрешности; в большинстве практических случаев даёт почти машинную точность результата.
- Стоимость: O(n)O(n)O(n) сложений, но на каждый вход ~5 операций вместо 1 (доступ к памяти тот же). Практический замедляющий множитель ≈ 1.5 ⁣− ⁣3×1.5\!-\!3\times1.53× по сравнению с наивным суммированием; дополнительная память O(1)O(1)O(1).
3) Другие техники и их оценка
- Neumaier (улучшение для случаев, когда |s| < |x|): почти как Kahan, но чуть проще обработка знаков. Стоимость и эффект аналогичны Kahan, лучше в некоторых ситуациях с большими слагаемыми.
- Pairwise (divide‑and‑conquer) / Tree summation: разбить массив пополам, суммировать рекурсивно и складывать результаты.
- Ошибка ограничена через глубину дерева: вместо γn−1\gamma_{n-1}γn1 появляется γ⌈log⁡2n⌉\gamma_{\lceil\log_2 n\rceil}γlog2 n . То есть погрешность растёт как O(log⁡n⋅ϵ)O(\log n\cdot\epsilon)O(lognϵ) вместо O(nϵ)O(n\epsilon)O(nϵ).
- Стоимость: общее число операций O(n)O(n)O(n) (приблизительно такое же), но накладные расходы на рекурсию/перестановку данных; хорошо распараллеливается (глубина O(log⁡n)O(\log n)O(logn)). Память: при рекурсивной реализации потребуется стек/временные буферы O(log⁡n)O(\log n)O(logn) или O(n)O(n)O(n) для итеративной перестановки.
- Сортировка по возрастанию абсолютных значений перед суммированием:
- Уменьшает явления отмены; хорошая практика при большом разбросе модулей.
- Стоимость: дополнительно O(nlog⁡n)O(n\log n)O(nlogn) сравнений и перестановок; итоговая погрешность близка к минимально возможной для фиксированной точности.
- Блочное суммирование + компенсация: разбить на блоки, в каждом блоке применять Kahan или pairwise, затем суммировать блоковые суммы pairwise. Хороший компромисс точности/параллелизма.
- Высокая точность (long double, quad, arbitrary precision):
- Простое и надёжное решение: вычислять в большей точности.
- Стоимость: аппаратный long double ~1.5–4×, программный quad или MPFR — десятки/сотни раз медленнее. Память и энергорасходы растут.
- Compensated expansions / double-double / Shewchuk: хранить сумму как расширение (несколько чисел), использовать точные преобразования TwoSum, FastTwoSum.
- Позволяет получить контролируемую (или практически точную) сумму, вплоть до произвольной точности, при возрастании числа компонент расширения.
- Стоимость линейно зависит от длины расширения; double‑double даёт ≈2× точности при ~3–6× затратах; более длинные расширения — пропорционально дороже.
4) Практические рекомендации
- Для большинства задач используйте Kahan (или Neumaier) — почти всегда существенное улучшение при малой цене.
- Если данные имеют сильно различающиеся порядки и точность критична — используйте pairwise summation (особенно в параллельной среде) или сортировку по модулю перед суммированием.
- Для гарантированной точности используйте более высокую точность или компенсированные расширения (double‑double / MPFR) — при этом будьте готовы к значительному замедлению.
- Для больших массивов в HPC: комбинация pairwise (для параллелизма) + локальный Kahan в блоках — хороший компромисс.
Краткая сравнительная сводка (точность / стоимость):
- Наивное: скорость O(n)O(n)O(n), память O(1)O(1)O(1), ошибка O(nϵ)O(n\epsilon)O(nϵ).
- Kahan/Neumaier: скорость O(n)O(n)O(n) (с фактором ≈1.5–3), память O(1)O(1)O(1), ошибка ≈O(ϵ)O(\epsilon)O(ϵ) в большинстве случаев.
- Pairwise: скорость O(n)O(n)O(n), лучше параллелизуется, ошибка O(log⁡n⋅ϵ)O(\log n\cdot\epsilon)O(lognϵ).
- Сортировка по модулю: точность очень улучшает, стоимость O(nlog⁡n)O(n\log n)O(nlogn).
- Высокая точность / расширения: стоимость ×(2…100+), точность существенно выше (до произвольной).
Если нужно, могу привести короткую реализацию Kahan и показать тот же пример численно (пошагово).
23 Апр в 16:56
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир