о сложении (программистское)
Aug. 8th, 2007 10:02 pmЧисла с плавающей точкой - вот еще одно в череде компьютерных задач, которыми я занимался мало и понимаю плохо. Сегодня я узнал, что числа с плавающей точкой надо складывать - если их много - не "наивно", а особенным образом. Мне неприятно, что я до сих пор этого не знал; или если знал, то забыл - кажется логичным, чтобы это упоминалось во время курса по численному анализу, который я брал на втором курсе много лет назад, но это был один из наиболее нелюбимых предметов, и после экзамена я с радостью забыл вообще все, что его касается.
Эта тема всплыла в одной внутренней рассылке на работе, где утверждалось, что средний выпускник CS-факультета этого не знает. Я уже давно не выпускник, но честно признаюсь: не знал. А вы, коллеги, знали?
Суть проблемы вот в чем. Предположим, вам надо суммировать миллион чисел с плавающей точкой, находящихся примерно в одном диапазоне - на расстоянии одного-двух порядков друг от друга. Тогда простой, как пробка, цикл от одного до миллиона с накоплением суммы - очень плохой способ суммировать эти числа. Дело в том, что вследствие того, что промежуточные суммы будут со временем намного больше среднего слагаемого, потери точности вследствие округления начнут играть заметную роль. Суммирование миллиона чисел таким образом легко может потерять 3-4 знака точности. Если, скажем, сложить таким образом миллион одинаковых чисел, а потом сравнить с результатом умножения исходного числа на миллион, это легко заметить.
В Перле:
Накопившаяся разница проявляется в шестом знаке после запятой, или, иными словами, четыре последних знака потеряны.
Что делать? В общем случае - помнить об ошибках накопления и учитывать их. В конкретном случае сложения большого количества примерно одинаковых чисел самым простым оказывается складывать их "равномерно". Вместо того, чтобы одна переменная копила сумму, сложим сначала пары чисел-соседей, и получим в два раза меньше промежуточных сумм. Их тоже сложим попарно, и так далее - log(N) шагов. Пример такого алгоритма можно увидеть на этой странице (написан на Питоне).
Эта тема всплыла в одной внутренней рассылке на работе, где утверждалось, что средний выпускник CS-факультета этого не знает. Я уже давно не выпускник, но честно признаюсь: не знал. А вы, коллеги, знали?
Суть проблемы вот в чем. Предположим, вам надо суммировать миллион чисел с плавающей точкой, находящихся примерно в одном диапазоне - на расстоянии одного-двух порядков друг от друга. Тогда простой, как пробка, цикл от одного до миллиона с накоплением суммы - очень плохой способ суммировать эти числа. Дело в том, что вследствие того, что промежуточные суммы будут со временем намного больше среднего слагаемого, потери точности вследствие округления начнут играть заметную роль. Суммирование миллиона чисел таким образом легко может потерять 3-4 знака точности. Если, скажем, сложить таким образом миллион одинаковых чисел, а потом сравнить с результатом умножения исходного числа на миллион, это легко заметить.
В Перле:
DB<1> $a=0.1234567890123456
DB<2> $x = 1_000_000*$a;
DB<3> for (1..1_000_000) { $y += $a; }
DB<4> x $y;
0 123456.789009695
DB<5> x $x
0 123456.789012346
DB<6> x $y-$x;
0 '-2.65109702013433e-06'
Накопившаяся разница проявляется в шестом знаке после запятой, или, иными словами, четыре последних знака потеряны.
Что делать? В общем случае - помнить об ошибках накопления и учитывать их. В конкретном случае сложения большого количества примерно одинаковых чисел самым простым оказывается складывать их "равномерно". Вместо того, чтобы одна переменная копила сумму, сложим сначала пары чисел-соседей, и получим в два раза меньше промежуточных сумм. Их тоже сложим попарно, и так далее - log(N) шагов. Пример такого алгоритма можно увидеть на этой странице (написан на Питоне).
no subject
Date: 2007-08-08 07:35 pm (UTC)т.е.
(4.2 - 2.2)^2 не всегда равно (5.2-3.2)^2
Факт очевидный, но на практике всегда о нем зыбываешь.
no subject
Date: 2007-08-08 07:36 pm (UTC)no subject
Date: 2007-08-08 07:39 pm (UTC)no subject
Date: 2007-08-08 07:40 pm (UTC)no subject
Date: 2007-08-08 07:43 pm (UTC)no subject
Date: 2007-08-08 07:47 pm (UTC)no subject
Date: 2007-08-08 07:49 pm (UTC)no subject
Date: 2007-08-08 07:52 pm (UTC)no subject
Date: 2007-08-08 07:58 pm (UTC)Более того, имхо, это проблема тех, кто преподаёт как раз математику, а не CS. Если курсы анализа и численных методов (в т.ч. компьютерных вычислений) не сопрягать, всё будет очень плохо.
no subject
Date: 2007-08-08 07:59 pm (UTC)Пример.
Пусть у вас есть число 1e+9 и миллион чисел 1; знаков в мантиссе, скажем, 8. Подумайте, что будет, если вы начнёте их складывать сразу с большего :)
Вариант с попарным сложением сначала самых маленьких тоже прикиньте.
no subject
Date: 2007-08-08 08:01 pm (UTC)no subject
Date: 2007-08-08 08:02 pm (UTC)Мой первоначальный вариант -- отсортировать и складывать от меньших к большим.
Если складывать сначала наименьшие пары, получится и сложений меньше, и потому потенциальная потеря точности будет ещё меньше.
no subject
Date: 2007-08-08 08:04 pm (UTC)Только медленно это и места много занимает.
no subject
Date: 2007-08-08 08:04 pm (UTC)There are still N-1 steps. :)
It's just that the "outer loop" of the algorithm runs log(N) times.
no subject
Date: 2007-08-08 08:04 pm (UTC)no subject
Date: 2007-08-08 08:06 pm (UTC)Does anyone believe that the difference between the Lebesgue and Riemann integrals can have physical significance, and that whether, say, an airplane would or would not fly could depend on this difference? If such were claimed, I should not care to fly in that plane. --Richard W. Hamming
no subject
Date: 2007-08-08 08:08 pm (UTC)no subject
Date: 2007-08-08 08:26 pm (UTC)??? No matter what algorithm you use, there will only be N-1 сложений
no subject
Date: 2007-08-08 08:28 pm (UTC)no subject
Date: 2007-08-08 08:29 pm (UTC)no subject
Date: 2007-08-08 08:41 pm (UTC)no subject
Date: 2007-08-08 08:45 pm (UTC)no subject
Date: 2007-08-08 08:47 pm (UTC)no subject
Date: 2007-08-08 08:52 pm (UTC)Dlja teh kto zanimaetsja chislennymi raschetami, skzazhem nauchnnymi, eto nachal'nye bazowye znanija. Ljuboj malo-malo prilichnyj chislennyj algoritm zabotitsja o wozmozhnoj poteri tochnosti.
no subject
Date: 2007-08-08 08:52 pm (UTC)написать математику сложения произвольных целых, записанных в виде строк. сконвертировать даблы в безразмерные целые (не более 1000 знаков) и поскладывать.
С умножением и делением сложнее.