Версия для печати


Загадки округления. Тестирование функций округления различных языков.
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1218

Алексей Михайличенко
дата публикации 25-04-2006 03:36

Загадки округления. Тестирование функций округления различных языков. Приложение к статье Загадки округления.

Чтобы поставить всех тестируемых в равные условия, вся работа велась через текстовые файлы. От программы требовалось считать файл с исходными данными, округлить числа, и построчно записать результат в другой файл. Таким образом, особенности внутреннего представления вещественных чисел на данном языке и соответствующих преобразований оставались на совести самой тестируемой программы.

Был подготовлен файл DATA.TXT с исходными данными: миллионом строк с числами с четырьмя знаками после запятой.

0.0000
0.0001
0.0002
0.0003
....
104164096.9996
104164096.9997
104164096.9998
104164096.9999

Числа охватывают диапазон от 0 до 100 миллионов. Предполагается, что этот диапазон значений наиболее интересен для учетно-бухгалтерских задач. Для исходных данных была подсчитана сумма неокругленных значений.

(Некоторые функции из-за особенностей реализации не могли принимать аргументы свыше 20 млн. Для них был подготовлен сокращенный вариант теста).

Затем были подготовлены образцовые файлы с результатами работы алгоритмов: арифметического (STD_A.TXT), бухгалтерского (STD_B.TXT), а также округления вверх и вниз, поскольку при некоторых SetRoundMode некоторые функции скатывались до этого. Для каждого алгоритма подсчитывалась эталонная сумма округленных значений.

Тестируемой програме, обещавшей реализовать один из алгоритмов, скармливался DATA.TXT. Полученный от нее файл RESULT.TXT сравнивался с образцовым STD_?.TXT соответствующего алгоритма (или с несколькими, если получалось ни то ни се). Считали количество ошибок (расхождений между файлами), и сумму округленных значений, полученных программой.

О чем говорит количество ошибок (графа 5 таблицы) ?

Ошибки возникали только при округлении пятерок. Учитывая, что в исходном DATA.TXT таких записей было 10 000, число ошибок, например, 5001 (строка 1), говорит о том, что лишь в половине случаев исследуемая программа совпадает с обещаемым алгоритмом. В графах 7, 8, 9 приведены примеры нехорошего поведения.

При наличии ошибок возникает второй вопрос — а куда клонит исследуемая программа сумму округленных значений? Ответ дает графа 6.

Как мы уже говорили, нам известна эталонная сумма "итого округленных" для каждого алгоритма. Для эталонного бухгалтерского она будет равна сумме неокругленных значений, как результат подавления статистической погрешности. Для арифметического — она будет на 50 руб. больше (так как 10,000 строк с пятерками дадут каждая по 0.5 коп. погрешности). Сравнив полученную сумму исследуемого алгоритма с эталонной, по полученной разнице можно сделать выводы:

  1. Если разница сумм (графа 6) соответствует по модулю количеству ошибок, умноженному на 0.01 (как в строке 1) то все ошибки совершались в одну сторону. Знак графы 6 показывает, куда именно.
  2. Если графа 6 меньше указанного в п.1, значит округление при отклонениях от образцового алгоритма происходило в разные стороны, частично (или полностью) компенсируя возникающее отклонение по сумме (строка 17).
  3. В пределе, если ошибки есть, но в результате сравнения с бухгалтерским алгоритмом разница получается 0 (или около того) (строка 2), это значит, что сумма округленных значений равна сумме неокругленных, то есть в результате работы программы все же происходит эффективное подавление статистической погрешности (ПСП), хотя и не бухгалтерским способом, а каким-то иным. Как видно, это довольно частый случай.

О чем еще хочется сказать. Характер распределения ошибок по числовой прямой для многих функций неравномерен: большие "чистые" участки перемежаются со всплесками ошибок. Видимо поэтому при выборочном тестировании авторами вручную ошибки не были замечены.


N
Язык, модуль, функция

(с указанием номера вопроса на Круглом столе, если функция взята оттуда).
Алгоритм
округления: Арифм., Бух,
(?) - пытаемся сравнивать непонятное поведение функции
Кол-во ошибок (расхож-
дений со сравнива-
емым алгорит-
мом)
Разница округленного итога с эталонной суммой алгоритма
Примеры ошибок
Примечание
Аргумент Должно быть по сравнива-
емому алгоритму

Получается (неверно)
1
2
3
4
5
6
7
8
9
10
Perl v5.8.2 built for cygwin-thread-multi-64int
1

встроенная, sprintf("%3.2f")

По документации sprintf вообще не обещает какого-либо внятного алгоритма округления. Тем лучше. Потестируем.
А(?) 5001
-50.01
0.0150
0.0450
0.0750
0.1050
0.02
0.05
0.08
0.11
0.01
0.04
0.07
0.10
Нет, это явно не арифметическое округление. Большое кол-во расхождений (5001), большая накопленная разница (-50.01).
2
Б(?)
4801
-0.01 0.0050
0.0150
0.0250
0.0650

0.00
0.02
0.02
0.06

0.01
0.01
0.03
0.07

Налицо наличие алгоритма ПСП (разница -0.01, почти 0), но не бух. окр, а какого-то другого (большое кол-во расхождений с бух. окр. : 4801).
3
Math::Round::nearest(0.01, n)
А
1428
-14.28
129.0050
129.0150
129.0350
129.0450
129.01
129.02
129.04
129.05
129.00
129.01
129.03
129.04

Ошибки начинаются с чисел свыше 110, распределение очень интересно.
4 Math::Round::round(n*100)/100
А
0
0



OK. Вообще, обертки 100/100 всегда оказываются лучше, чем спец. функции с указанием знака.
5
Math::Round::round_even(n*100)/100
Б
0
0

OK.
6
Math::Round::nearest_rand
Б
5000
35.72


Реализация экзотического алгоритмя Random Rounding.

На целых числах работает. Практически не работает на дробных значениях, преимущественно сваливается на Арифм. алгоритм. Пришлось обернуть...
7
Math::Round::nearest_rand(n*100)/100 Б
5037
0.53

Неплохо. Накопление погрешности подавлено практически полностью. Расхождения анализировать смысла нет — они случайные.
8
еще раз
Б
4929
0.57

FoxPro 2.6 DOS
9
ROUND(n, 2) А
0
0

OK
Excel 9.0.3821 SR-1
10
ОКРУГЛ(A1;2) А 0 0

OK. Не зря считается эталоном в расчетах.
11 (VBA) Round(n1, 2)
Б
0
0



OK
12 (VBA) CLng(n1 * 100) / 100

Б
0
0



OK до 20 млн (больше не может)
QBASIC
13 INT(n * 100 + .5) / 100

А(?)
4800 -48.00 0.0150
0.0450
0.0750
0.1050
0.02
0.05
0.08
0.11
0.01
0.04
0.07
0.10
Вообще-то по документации это чистый А...
Б(?) 5000 2.00
0.0050
0.0150
0.0250
0.0650

0.00
0.02
0.02
0.06


0.01
0.01
0.03
0.07

...но неожиданно оказался с алгоритмом ПСП, опять же не бух. окр, но довольно успешным.
14 CINT(n * 100) / 100

Был сделан отдельный тест до 20 млн. (больше оно не может).
Б
4368
0.00
0.0050
0.0150
0.0250
0.0650
0.00
0.02
0.02
0.06
0.01
0.01
0.03
0.07
Эффективнейшее ПСП, опять же неизвестное.
Delphi 7 Ent
SetRoundMode
15 RoundTo

rmUp Округление вверх
8449
84.49
0.0300
0.0500
0.0600
0.0700
0.03
0.05
0.06
0.07
0.04
0.06
0.07
0.08
Излишне усердное
16 rmDown Округление вниз
776
-7.76
0.1300
0.2600
0.5100
0.5200
0.13
0.26
0.51
0.52

0.12
0.25
0.50
0.51
Излишне усердное
17 rmNearest Б(?)
5003
-13.45
0.0150
0.0250
0.0550
0.0650
0.02
0.02
0.06
0.06
0.01
0.03
0.05
0.07
ПСП плохого качества, лишь в 50% совпадающее с Бух. окр.
18 SimpleRoundTo
rmUp А
1543
-15.43
0.0550
0.0950
0.1250
0.2550
0.06
0.10
0.13
0.26
0.05
0.09
0.12
0.25
Значительно врущее вниз
19 rmDown А
771
-7.71
0.0650
0.2550
0.2650
0.3150
0.07
0.26
0.27
0.32
0.06
0.25
0.26
0.31
врущее вниз, но ближе всего к ожидаемому Арифм. окр.
20 rmNearest А(?)
6344
-63.44
0.0150
0.0450
0.0550
0.0750
0.02
0.05
0.06
0.08
0.01
0.04
0.05
0.07
явно не оно
21 Б
5004
-13.44
0.0050
0.0150
0.0250
0.0550
0.00
0.02
0.02
0.06
0.01
0.01
0.03
0.05
Включился ПСП. Очень похоже на RoundTo
22 (Round(MyFloat*100))/100

Вопрос 17851, Светлов Виктор, Shabal
rmUp Округление вверх 4798
47.98
0.0500
0.0700
0.0900
0.1000
0.05
0.07
0.09
0.10
0.06
0.08
0.10
0.11
Проблемы с числами на 00 — в половине случаев округляет неверно, и все вверх.
23 rmDown Округление вниз 4802 -48.02
0.0100
0.0200
0.0300
0.0400
0.01
0.02
0.03
0.04
0.00
0.01
0.02
0.03
Та же проблема, только вниз.
24 rmNearest
Б
570
-0.04
0.2950
1.0550
1.0650
1.1750
0.30
1.06
1.06
1.18
0.29
1.05
1.07
1.17
Отличное ПСП, процесс на 96% совпадает с Бух. окр. Но 570 отличий все же наделало.
25 RoundTo(n + 0.0001, -2)
Вопрос 36234, Shabal
rmNearest А
3655
36.55
0.0249
0.0349
0.0649
0.0849
0.02
0.03
0.06
0.08
0.03
0.04
0.07
0.09
Несколько рьяное Арифм. окр. Проблема с числами на xx49, но не со всеми, а в 37% случаев. (а что если еще меньшее прибавлять?)
26
RoundTo(n + 0.00001, -2)
rmNearest А
0
0.00



А если прибавлять еще меньше? OK. 1e-5...1e-8
27 RoundTo(n + 1e-10, -2);
rmNearest А
1272
-12.72
1185920.1150
1185920.1250
1185920.1350
1185920.1450
1185920.12
1185920.13
1185920.14
1185920.15
1185920.11
1185920.12
1185920.13
1185920.14
А если прибавлять еще меньше?

Ошибки начинаются примерно с 1 млн.
28 Функция от ЮГ (Вопрос 36234) Extended rmNearest Б
4703
0.09
0.2950
1.0150
1.0350
1.0550
0.30
1.02
1.04
1.06
0.29
1.01
1.03
1.05
Еще одно неплохое ПСП, но опять же загадочное...
29 Оболочка вокруг Ceil, Вопрос 38097, Python

переполняется на 20 млн, т.к. Ceil: integer.. Отдельный тест.
rmUp Округление вверх 8736
87.36
0.0100
0.0200
0.0300
0.0400
0.01
0.02
0.03
0.04
0.02
0.03
0.04
0.05
Почти все (87%) что на xx00 — переокругляет вверх.
30 rmDown Округление вверх 0
0.00

OK. Парадоксально, но факт.
31 rmNearest Округление вверх 771
7.71
0.0700
0.1400
0.2300
0.2800
0.07
0.14
0.23
0.28
0.08
0.15
0.24
0.29
Некоторые (8%) из тех что заканчиваются на 00 — переокругляет вверх.
32 StrToFloat(FloatToStrF(n, ffFixed, 15, 2));
rmUp, rmNearest А
0
0.00
OK.
33 rmDown А(?)
4800
-48.00
0.0050
0.0150
0.0550
0.0650
0.01
0.02
0.06
0.07
0.00
0.01
0.05
0.06

34 Б(?)
5000 2.00 0.0150
0.0250
0.0450
0.0550
0.02
0.02
0.04
0.06
0.01
0.03
0.05
0.05

35
Trunc(n*100 + 0.5) / 100 (Вопрос 39942)
rmUp А 0
0.00



OK.
36
rmDown А(?) 4800
-48.00
0.0050
0.0150
0.0550
0.0650
0.01
0.02
0.06
0.07
0.00
0.01
0.05
0.06

37
Б(?) 5000
2.00
0.0150
0.0250
0.0450
0.0550
0.02
0.02
0.04
0.06
0.01
0.03
0.05
0.05

38 rmNearest А 571
-5.71
0.2650
0.2950
0.5250
0.5850
0.27
0.30
0.53
0.59
0.26
0.29
0.52
0.58

39
Вопрос 40789, функция N 1
rmNearest А
1278
-12.78
1.0050
1.0150
8.0650
8.0750
1.01
1.02
8.07
8.08
1.00
1.01
8.06
8.07

40 Вопрос 40789, функция N 2
rmNearest А
5088
-50.88
0.0150
0.0450
0.0750
0.1050
0.02
0.05
0.08
0.11
0.01
0.04
0.07
0.10

41 Вопрос 40789, функция N 1e
(вариант, когда все заменено на extended)
rmNearest А 1238
-12.38
8.0650
8.1550
129.1050
129.1350
8.07
8.16
129.11
129.14
8.06
8.15
129.10
129.13
заменой на Extended уничтожена разница между 1 и 2, но не устранена ошибка.
42 Вопрос 40789, функция N 2e
rmNearest А 1238
-12.38
8.0650
8.1550
129.1050
129.1350
8.07
8.16
129.11
129.14
8.06
8.15
129.10
129.13

43 Вопрос 40789, функция N 3
меняется внутри самой функцией
А 102
-1.02
1.0050
1.0150
8.0650
8.0750
1.01
1.02
8.07
8.08
1.00
1.01
8.06
8.07
настолько мало ошибок, что удивительно. Все таки алгоритм может быть либо верным, либо неверным, но чтобы неверным в 1% случаев...
44
Вопрос 40789, функция N 1e А 50
-0.50
8.0650
8.1550
129.1050
129.1350
8.07
8.16
129.11
129.14
8.06
8.15
129.10
129.13
а как вам в 0.5 % ???
Если алгоритм сомнителен, в его результаты хоть иногда заглядывают и пересчитывают. А если он считается надежным, то его не проверяют и забывают. А он подгадит...

45
John Herbster DecimalRoundExt
меняется внутри самой функцией А
0
0



ОК
46
John Herbster DecimalRoundExt Б
0
0



OK. Вот достойный человек!
MySQL 3.23.58-nt
47 ROUND(n1, 2)

А
9428
-94.28
0.0050
0.0150
0.0250
0.0450
0.01
0.02
0.03
0.05
0.00
0.01
0.02
0.04
Алгоритм, завиcящий от системных библиотек.

xx50 округляет вниз, но не всегда, а в 94% случаев.
48 FLOOR(n1*100 + 0.5) / 100

А
570
-5.70
0.1450
0.2850
0.5650
0.5750
0.15
0.29
0.57
0.58
0.14
0.28
0.56
0.57
Почти хорошее арифм.округление, врет только в 5.7 % случаев, и все вниз.
49
FLOOR(n1*100 + 0.500001) / 100
А
0
0



ОК
PostgreSQL 7.4.8 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.3.5 20050117 (prerelease) (SUSE Linux)
50 ROUND(n1, 2)
А
0
0



ОК, также trunc(n1+0.005, 2)