Ниже мы приводим примеры арифметических операций над некоторыми числами с плавающей точкой, которые приводят к неверным результатам. Эти результаты не зависят от платформы, на которой реализованы вычисления.
В настоящей статье мы не приводим теоретических выкладок, которые объясняют причину появления этих ошибок. Это тема следующего топика. Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики. Рассматриваемые здесь примеры неумолимо наталкивают на мысль о целесообразности использования формата с плавающей точкой в том виде, как его трактует стандарт IEEE754.
Отметим, что причина ошибочных вычислений с ЧПТ, главным образом, обусловлена ни ошибками округления, устранению которых в стандарте уделяется большое внимание, а самой природой конвертации десятичных и двоичных чисел.
Будем рассматривать два основных формата для ЧПТ — float и double. Напомним, что формат float позволяет представлять до 7 верных десятичных цифр в двоичной мантиссе, содержащей 24 разряда. Формат double представляетдо 15 верных десятичных цифр в мантиссе, содержащей 53 двоичных разряда.
Непредставимые в двоичном машинном слове десятичные числа, после приведения их к десятичному виду, содержат как верные цифры так и «хвосты» из неверных цифр. Эти «хвосты» и являются источником ошибочных вычислений десятичных действительных ЧПТ с помощью двоичной арифметики. Покажем это на примерах.
СУММА
Итак, сначала рассмотрим сумму двух следующих действительных чисел, представленных в формате float, каждое из которых имеет по 7 верных значащих цифр:
0,6000006 + 0,03339874=0,6333993 4?0,6333993
Все вычисления будем проводить в формате float. Для наглядности будем использовать числа в распакованном виде. Представим наши десятичные числа в нормализованном двоичном виде:
0.6000006 ? 1.001100110011001101001?2?^(-1)
0.03339874?1.00010001100110100011110?2?^(-5)
Если полученные двоичные коды наших чисел опять представить в десятичном виде, то получим следующие значения:
0.6000006 ? 0.6000006 198883056640625?0.6000006 2
0.03339874?0.03339873 9993572235107421875?0.0333987 4
Здесь каждое число, округленное до 7 верных цифр мы отделили от «хвоста» пробелом. Эти «хвосты» получились в результате обратной конвертации чисел из двоичного кода, записанного в машинной мантиссе, в десятичный код.
Сумма наших чисел в двоичном 24-х разрядном виде даст следующий результат:
1.001100110011001101001?2?^(-1) + 1.00010001100110100011110?2?^(-5)? 1.0100010001001100111011?2?^(-1) ?0,6333994
Тот же результат будет получен, если просуммировать числа с «хвостами»:
0,6000006 2+ 0,0333987 4= 0,6333993 6? 0,6333994
Как мы видим, после округления до 7 значащих цифр, здесь получен результат, отличный от того, который получился при суммировании десятичных чисел на калькуляторе. Правила округления двоичного представления чисел, заложенные в стандарте IEEE754, не решают проблему точного вычисления, рассмотренных здесь чисел. В нашем случае причина ошибки кроется в сочетании цифр, стоящих после последних верных цифр десятичных слагаемых, о которых, априори, ничего не известно.
Приведем еще один пример сложения. Просуммируем на калькуляторе два следующих действительных числа, каждый из которых содержит по 7 верных десятичных значащих цифр:
6543.455+12.34548=6555.80048?6555.800
Приведем наши десятичные слагаемые к двоичному нормализованному виду:
6543.455=1.10011000111101110100100?2?^12=6543.455 078
12.3454810 = 1.10001011000011100010110?2?^3 ? 12.345 48
Сумма этих слагаемых в двоичном виде даст следующее двоичное число:
1.10011001101111001101?2?^12?6555.80078125?6555.801
Здесь мы снова получили результат, отличающийся от того, который вычислен на калькуляторе для неконвертируемых десятичных чисел.
УМНОЖЕНИЕ
Такая же проблема неточных вычислений возникает при нахождении произведения некоторых ЧПТ, представленных в двоичном коде в формате float. Для примера рассмотрим произведение следующих действительных чисел:
0.06543455*139=9.095402 45?9.095402
Представим в этом выражении сомножители в нормализованном двоичном виде:
0.06543455=1.00001100000001010001101?2?^(-4)
139=1.0001011?2?^10
Результатом перемножения наших чисел в двоичном виде будет число:
1.00001100000001010001101?2?^(-4) ? 1.0001011?2?^10 = 1001.00011000011011000101 ?9.095403
Мы получили ошибку в младшем разряде произведения, которую невозможно исправить путем округления числа, представленного в двоичном коде. Такие ошибки принято называть фатальными.
ДЕЛЕНИЕ
Аналогично умножению, операция деления в формате float для некоторых ЧПТ также приводит к фатальным ошибкам. Рассмотрим следующий пример:
131/0.066?1984.848
Представим делимое и делитель в двоичном формате, в нормализованном виде:
13110 = 1.0000011?2?^7
0.066= 1.00001110010101100000010?2?^(-4)
Частное от деления наших чисел будет следующим:
1.0000011?2?^7/1.00001110010101100000010?2?^(-4)=
= 1.11110000001101100100111?2?^10 = 1984.848 5107421875?1984.849
Мы видим, что полученный здесь результат не соответствует правильному значению, вычисленному на калькуляторе или вручную.
ВЫЧИТАНИЕ
Вычитание — это та операция, которая полностью дискредитирует идею использования двоичной арифметики для вычисления десятичных значений ЧПТ. В результате этой операции, в некоторых случаях, можно получить результат даже близко не соответствующий действительности. Продемонстрируем это на примере.
Пусть уменьшаемое у нас будет равно 105.3256. Вычтем из него число 105.32. Разность этих чисел, вычисленная вручную, будет равна:
105.3256-105.32=0.0056
Представим десятичное уменьшаемое и десятичное вычитаемое в нормализованном двоичном виде:
105.3256 = 1.10100101010011010110101?2?^6?105.3255 997 041015625
105.32= 1.10100101010001111010111?2?^6?105.32
Найдем разность этих чисел в двоичном виде:
1.10100101010011010110101?2?^6-1.10100101010001111010111?2?^6= 1.01101111?2?^(-8)
После преобразования этого числа в десятичный вид получим:
1.01101111?•2?^(-8)= 0.005599976
Мы получили результат, существенно отличающийся от того, который ожидали.
ОШИБКИ В ФОРМАТЕ ДАБЛ
Ситуацию с фатальными ошибками не спасает и формат более высокой точности, например double. Как мы выше уже отмечали, это объясняется самой природой конвертации чисел из одной системы счисления в другую. Покажем это на примерах.
В качестве инструмента для проверки правильности наших рассуждений будем использовать Excel 2009, в котором реализованы вычисления в строгом соответствии со спецификацией стандарта IEEE754.
Проведем следующие вычисления, используя средства Excel 2009. Формат ячеек выберем числовой, с 18 знаками после запятой. Для нахождения суммы запишем в ячейки таблицы Excel следующие числа:
A1= 0,6236
A2= 0,00661666666070646
В ячейке А3Excel получим сумму этих чисел:
А3=А1+А2=0,6236+0,00661666666070646?0,630216666660707
Если посчитать эту сумму вручную или на калькуляторе, то получится число:
0,6236+0,00661666666070646?0,630216666660706
Которое в младшем разряде не совпадает с тем, что получено в Excel.
Посмотрим, к чему приводит операция вычитания в Excel. Запишем в ячейки следующие числа:
А1= 123456,789012345
А2= 123456
В ячейке А3 найдем разность этих чисел. Она будет равна:
А3=А1-А2=0,789012345005176
А мы ожидали получить число:
123456,789012345-123456=0, 789012345
Выводы делайте сами!
В заключении приведем пример того, как быстро может расти ошибка, если использовать двоичную арифметику для вычисления десятичных действительных чисел даже без операции вычитания.
Запишем в ячейки таблицы Excel следующие числа:
А1= 0,500000000660006
А2 = 0,0000213456548763
А3 = 0,00002334565487363
А4 = 0,000013345654873263
В ячейке А6 запишем формулу =A1/5+A2. После чего в ней будет получен результат.
A6= A1/5+A2= 0,100021345786878
В ячейке A7 произведем следующие вычисления:
A7=A6/3+A3=0,0333637942504995
А теперь вычислим
A8=A7/4+A4=0,00835429421749813
Проведем те же вычисления на калькуляторе. Вычисления будем производить с точностью до 15 десятичных значащих цифр. Цифры, которые стоят правее младшего значащего разряда, согласно правилам арифметики, будем округлять до ближайшего целого. В результате будем иметь:
A1/5=0,500000000660006/5=0,100000000132001 2?0,100000000132001
A1/5+A2=0,100000000132001+0,0000213456548763? 0,100021345786877
( A1/5+A2)/3=0,100021345786877/3?0,0333404485956257
( A1/5+A2)/3+A3=0,0333404485956257+0,00002334565487363? 0,0333637942504993
[( A1/5+A2)/3+A3]/4=0,0333637942504993/4?0,00834094856262483
[( A1/5+A2)/3+A3]/4+A4=0,00834094856262483+0,000013345654873263=0,00835429421749809
Сравнив результат, полученный при вычислениях с соблюдением правил арифметических операций с тем, что получился в Excel, можно сделать вывод, что использование двоичной арифметики для десятичных чисел с плавающей точкой может приводить к совершенно непредсказуемым результатам.
Комментарии (197)
4dmonster
13.09.2016 09:43+7Так это всё общеизвестные факты.
Всё равно что написать статью «НЕВОЗМОЖНОСТЬ ПРЕДСТАВЛЕНИЯ ЦЕЛЫХ ОТРИЦАТЕЛЬНЫХ ЧИСЕЛ С ПОМОЩЬЮ ТЕКУЩЕЙ РЕАЛИЗАЦИИ ДВОИЧНОЙ СИСТЕМЫ СЧИСЛЕНИЯ В ЭЛЕКТРОННЫХ ВЫЧИСЛИТЕЛЬНЫХ МАШИНАХ.»Innotor
13.09.2016 09:48+1В статье речь идет о результатах арифметических вычислений действительных десятичных чисел с использованием двоичной арифметики.
4dmonster
13.09.2016 10:21Я просто по аналогии привёл пример.
В учебниках же пишут про float и double, что они не точные.
Ну а в качестве иллюстрации нагляднее такое поведение — просто преобразование одного и того же числа 0.18 в VBA:
из double в float
?csng(cdbl(0.18)) 0.18
из float в double
?cdbl(csng(0.18)) 0.180000007152557
Innotor
13.09.2016 11:25-1В учебниках же пишут про float и double, что они не точные.
Да, это всем известно. Но в статье речь идет не об ошибках преобразования десятичных чисел в двоичные и обратно, а к последствиям, к которым эти преобразования приводят при простейших арифметических вычислениях.4dmonster
13.09.2016 11:28Согласен, хотя в очень старом учебнике об этом тоже было написано, и была даже пара примеров.
Innotor
13.09.2016 11:39-1К сожалению я не нашел этого очень старого учебника и поэтому ничего возразить не могу.
Danov
13.09.2016 23:10Ребятам даю пример:
0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 != 1.0
и далее объясняю нюансы.
Сам пример даю так:
a = 0; while (a != 1) a += 0.1;
Какое значение будет после окончания цикла? Или сколько итераций цикла будет выполнено?
Шок гарантируется. Потому хорошо запоминается, что вещественные числа не стоит проверять на равенство и неравенство.
rafuck
13.09.2016 10:18+91) Все ваши примеры эксплуатируют один и тот же эффект: не каждая конечная десятичная дробь является конечной дробью в двоичной системе счисления. Тут как бы на IEEE754 не стоит всех собак вешать. Скорее, следует сетовать на конечность представления чисел (каким бы оно не было).
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.Innotor
13.09.2016 11:11" Все ваши примеры эксплуатируют один и тот же эффект: не каждая конечная десятичная дробь является конечной дробью в двоичной системе счисления. Тут как бы на IEEE754 не стоит всех собак вешать. Скорее, следует сетовать на конечность представления чисел (каким бы оно не было)".
Вы совершенно правы. И про IEEE754 вы правы. Стандарт работает с теми числами, которые ему предлагают. Но как Excel понять, где правильное число, а где не правильное. Если в большинстве случаев на выходе правильный результат, а в некоторых, случайных, не правильный, как быть?
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.
Но почему же Excel использует неустойчивый алгоритм стандарта IEEE754 для сложения, вычитания и проч. Если на выходе мы имеем непредсказуемый результат?4dmonster
13.09.2016 11:18Для скорости и экономии памяти конечно!
Ведь есть библиотеки для точных вычислений. Но они, естественно, медленнее чем железная реализация. И железная реализация сделана такой какая она есть ради скорости и экономии памяти.Innotor
13.09.2016 11:35Если Вас устраивает экономия памяти, при которой вы получаете результат с относительной погрешностью 0.6% на одной операции, то конечно проблем не будет.
0,0056-0,005599976 =0,005599976/105.3256 ?0,006*100=0.6%4dmonster
13.09.2016 11:40Если нужна точность — используют или фиксированную точку или готовые библиотеки или пишут что-то самодельное.
rafuck
13.09.2016 12:55+1Вот смотрите, простой пример:
float af = 0.1; double ad = 0.1; printf("af - ad = %le;\n", af - ad);
Ответ:
af - ad = 1.490116e-09;
Когда я написалfloat a = 0.1
я уже согласился на определенную погрешность представления десятичного значения 0.1, являющегося в двоичном представлении бесконечной периодической дробью. Далее я делаю миллион вычислений по определенному алгоритму и должен быть уверен, что данная ошибка представления (да и все остальные ошибки, возникающие при каждом вычислении) в результате не накопятся и не превысят определенный порог. Это и называется устойчивым алгоритмом.
К слову, вот этот алгоритм во float неустойчив:
const int N = 1e8; float af = 0.1; float S = 0; for(int i=0; i<N; ++i){ S += af; } printf("error = %le\n", 0.1*N - S);
Ответ:
error = 7.902848e+06
Innotor
13.09.2016 15:08Далее я делаю миллион вычислений по определенному алгоритму и должен быть уверен, что данная ошибка представления (да и все остальные ошибки, возникающие при каждом вычислении) в результате не накопятся и не превысят определенный порог.
В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.rafuck
13.09.2016 18:36Какой именно пример из приведенных вами доставляет такие страдания? Я просто не понимаю, что вы имеете в виду под «младшим разрядом» и как он соотносится с порядком самих чисел.
Вообще, я и не говорил, что формат, принятый для представления вещественных чисел, идеален. Да, можно придумать примеры, когда получаются неожиданные результаты. Да, иногда нужно знать, как все устроено, чтобы не наступать на грабли.Innotor
13.09.2016 20:46Какой именно пример из приведенных вами доставляет такие страдания?
Последний пример в статье, не то что доставляет страдания, но заставляет ощущать некий дискомфорт, когда тебя так нагло «обвешивают». В мантиссе, как в десятичной, так и в двоичной, имеются цифры, которые считают справа налево от первой значащей цифры справа. Вот ее я и называю младшим разрядом в мантиссе числа (не путать с машинной мантиссой). К порядку чисел это никакого отношения не имеет.
Да, можно придумать примеры, когда получаются неожиданные результаты.
А вы уверены, что такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой?rafuck
14.09.2016 00:43В последнем примере вы сравниваете расчеты в Excel с вычислениями на калькуляторе, при этом каждое действие на калькуляторе округляете до 15 знака после запятой. Так?
В итоге результат Excel и калькулятор совпадают в 15 знаках после запятой. Я все правильно понимаю?
Простите, может я слишком хочу спать и неправильно интерпретирую ваши 18 значные дроби.Innotor
14.09.2016 07:16Правильно. Если вычислять по правилам арифметики с точностью до 15 знака, с калькулятором мы делаем все верно. Если вычислять до 15 знака после запятой в формате дабл, и значащие цифры считать от точки в ненормализованном числе, то хвост не мешает. Арифметические операции не приближают неверные цифры к точке. Но, если считать по количеству значащих десятичных цифр, или от точки, в нормализованном числе, то хвост попадает в область верных цифр или участвует в образовании младшей значащей десятичной цифры. Проблема в том, что отсечение хвоста в двоичном коде увеличивает хвост в десятичном представлении и приближает неверные десятичные цифры к точке. Чтобы все было корректно, надо на каждом операционном шаге десятичное представления результата округлять до нужного значения
rafuck
14.09.2016 09:04Вот честно, два раза перечитал ваш комментарий и ничего не понял. Вы сравниваете два результата вычислений. Один провели сами с точностью до 10^(-15), другой доверили экселю. Получили совпадение в 15 знаках после запятой. И вам почему-то это не нравится. А вот я бы, наверное, не доверял скорее результату ваших вычислений с арифметическими округлениями, а не округлением до четного.
Innotor
14.09.2016 10:19В соответствие с правилами арифметических операций, чтобы сложить/умножить/разделить два числа с точностью до N-го знака после запятой, каждое число должно быть округлено до N знаков. Делая арифметические операции над двоичными числами, которые приблизительно представляют исходные десятичные, мы не можем выполнить этого требования, т.к. уменьшение разрядности двоичного числа делает его менее точным в его десятичном представлении. Таким образом, мы имеем нарушения правил сложения десятичных приблизительных чисел, в результате которого мы получаем неверный результат.
rafuck
14.09.2016 10:311.25/0.25 = 5
теперь, следуя вашей логике, я округляю до десятых, надеясь получить один правильный знак после запятой (N = 1)
1.3/0.3 = 4.(3)
Сколько верных знаков после запятой у меня получилось?Innotor
14.09.2016 11:26Да, по правилам арифметики так и получается. Как говорится «что выросло, то выросло»
rafuck
14.09.2016 11:37Я цитирую вас:
В соответствие с правилами арифметических операций, чтобы сложить/умножить/разделить два числа с точностью до N-го знака после запятой, каждое число должно быть округлено до N знаков.
я вам показал только что, что это не так.
vadimr
14.09.2016 11:16Так компьютер-то работает с двоичными числами. Вся проблема, в данном случае, оттого, что Вы зачем-то записываете двоичные, по своей технической природе, числа в приближённой десятичной форме.
Innotor
14.09.2016 11:38К сожалению, если бы мы в жизни использовали шестнадцатиричную, восьмеричную или, на худой конец, двоичную систему счисления, проблем бы с компьютерными вычислениями не было. Но мы, как правило, оперируем с десятичными числами и пытаемся заставить компьютер их понимать. И получать информацию от него мы хотим в десятичном виде, как привыкли.
vadimr
14.09.2016 11:43Зачем вам в жизни вещественные числа? В типичном случае, компьютер получает информацию от датчиков в двоичной форме, обрабатывает её в двоичной форме и выдаёт на исполнительные механизмы тоже в двоичной форме. А выдача человеку на экран носит чисто справочный характер.
В Вашем примере с ядерной установкой, десятичной системе просто неоткуда взяться.Innotor
14.09.2016 12:32Вся беда в том, что как только нам приходится делить два целых числа в десятичном или в двоичном коде, возникновение действительных, рациональных, дробных чисел неизбежно. Вопрос только в том, какую точность мы желаем получить при ограниченных аппаратных ресурсах.
DistortNeo
14.09.2016 13:08Если бы мы использовали 16-ричную систему, то проблемы бы остались такими же.
vadimr
14.09.2016 18:36Ну, проблемы неточности представления двоичных дробей не было бы.
DistortNeo
14.09.2016 19:45Почему? Все равно округлять бы пришлось из-за ограничения на длину мантиссы.
vadimr
14.09.2016 20:13Если бы в длину мантиссы по количеству знаков не уложились, то да. Но короткие шестнадцатиричные дроби кодируются точно, в отличие от некоторых десятичных, вроде 0.2.
Часть проблем автора поста связаны с тем, что не все конечные десятичные дроби имеют конечное двоичное представление.Innotor
14.09.2016 20:4016-ричные дробные числа, не переполняющие разрядную сетку мантиссы конечно могут быть точно представлены в двоичном коде. Это две соизмеримые системы счисления. Но, когда приходится делить два таких числа друг на друга мы опять можем получить бесконечную дробь:
0.9 ? 0.7 = 1.49(249) в 16-ричной системе счисления.
DistortNeo
14.09.2016 20:40Смотря что эти 0.2 означают. Если это именно точное значение, то логичнее его представлять как 1/5, а не 0.2.
Если же это результат физического измерения (масса, например), то точность измерения будет на порядки ниже точности округления. Если значение измерено с точностью до 1 знака, то мы можем смело 0.2 представить как 21/256, и ничего не потеряем.
Refridgerator
14.09.2016 10:34В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.
А это уже вопрос используемого алгоритма и контроля погрешностей. В частности, Алгоритм Кэхэна как раз и используется для суммирования большого количества элементов без фатального накопления погрешности.Innotor
14.09.2016 10:56Алгоритм Кэхэна работает за счет привлечения еще одного операционного программного регистра в дополнение к аппаратному. Это все равно, что увеличить в два раза аппаратный регистр. При этом проблема неверных цифр («хвоста») не пропадает.
Refridgerator
14.09.2016 11:08Это все равно, что увеличить в два раза аппаратный регистр.
Это как? По моей логике, «увеличить в два раза аппаратный регистр» = «увеличить точность в 2 раза», в то время как в алгоритме Кэхэна ничего подобного не наблюдается. Там просто контролируется накопление погрешности. Опять же, с ограниченной точностью.Innotor
14.09.2016 11:28А алгоритм Кэхэна для того и придуман, чтобы увеличить точность вычислений в 2 раза.
rafuck
14.09.2016 11:41Ничего подобного, абсолютная точность алгоритма совпадает с точностью переменной, в которой копится сумма. Относительная погрешность лучше — это да.
Refridgerator
14.09.2016 11:41А вы можете обосновать, что точность увеличивается именно в 2 раза, а не, скажем, в полтора? Откуда вообще взялась эта цифра? Ни в русской, ни в английской википедии её нет.
Innotor
14.09.2016 12:43Конечно же не в 2 раза, а в 10^N для десятичных чисел или в 2^N для двоичных чисел. Где N — количество разрядов, выделенное для учета дробных значений, которые вышли за пределы разрядной сетки операционной мантиссы. Учет этих значений уменьшает абсолютную погрешность и как следствие относительную.
Refridgerator
14.09.2016 13:06Выглядит ещё менее убедительно. Если честно, выглядит так, как будто вы эту формулу взяли просто из головы. Как минимум потому, что в ней отсутствует количество суммируемых элементов. Кроме того из неё следует, что если для хранения накапливаемой погрешности вместо double взять long double, это приведёт к экспоненциальному росту точности. Что сомнительно. Ведь вычисления производятся по-прежнему в оригинальном формате.
Innotor
14.09.2016 13:37В примере из Википедии для старших цифр десятичных чисел отведено 6 разрядов и столько же для погрешности. В общей сложности получается, что число представляется 6+6+12 разрядами. Каждый новый разряд увеличивает точность представления числа в 10 раз для десятичных чисел. Вот и получается, что за счет дополнительного программного регистра точность повысилась в 10^6 раз.
DistortNeo
13.09.2016 16:51А вы не используйте алгоритмы, которые допускают такое накопление.
Например, для обучения свёрточных нейронных сетей зачастую достаточно даже не float, а half-float (2 байта).
Andy_U
13.09.2016 11:12Десятичные числа (с ограниченным числом разрядов) — рациональные(т.е. лишь счетное подмножество вещественных числе), а не вещественные. Как, кстати, и двоичные.
Innotor
13.09.2016 11:14Именно этот факт и приводит к тем ошибкам, о которых в статье говорится.
Andy_U
13.09.2016 13:16+2Я лишь хотел заметить, что нет такого математического понятия «десятичные действительные числа», которое Вы использовали не только в комментарии, на который я написал ответ, но, и, если внимательно посмотреть, то и в статье. Возможно это и есть причина, по которой многие ваши утверждения математически некорректны. Например, Вы пишете в разделе про умножение:
Представим в этом выражении сомножители в нормализованном двоичном виде:
0.06543455=1.00001100000001010001101?2?^(-4)
139=1.0001011?2?^10
Так вот, число 0.06543455 лишь приближенно равно 1.00001100000001010001101?2?^(-4), (точное двоичное значение — двоичная периодическая дробь, т.е. число с бесконечным количеством знаков), что собственно говоря и является причиной, почему 139*0.06543455 !=139*1.00001100000001010001101?2?^(-4)Innotor
13.09.2016 15:24Так в чем неточность? В термине «действительное число»? Да, я с вами согласен, это число рациональное. Почему-то в литературе, в том числе в стандарте IEEE754, дробные рациональные числа называются действительными. Я не стал менять эту традицию, т.к. для данного случая это не принципиально.
Andy_U
13.09.2016 16:39+4Хорошо. Но теперь Вам стало понятно, что равенство (из Вашей статьи):
0.06543455=1.00001100000001010001101?2?^(-4)
лишь приближенное? Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.
Теперь заменяем везде в статье, где слева десятичная дробь, а справа двоичная, знак точного равенства на знак приближенного равенства. И видим теперь, что Ваш текст «разошелся» с математикой. Попробуйте его скорректировать чтобы и математика не пострадала, и идея вашей статьи. Я сомневаюсь, что это возможно.
Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления? А если бы в компьютере была использована троичная система счисления? Или пользователь бы троичную систему счисления использовал, а компьютер десятичную? Где потерялась симметрия?
Ну и наконец, во всех физических и математических расчетах рациональных чисел не бывает. Чему там равна скорость света? А никто точно не знает. Лишь доверительный интервал известен (между двумя рациональными числами). Плевать на ошибку оцифровки в значительном числе случаев.
Innotor
13.09.2016 18:07-2Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.
Вы правы, в приведенном равенстве надо поставить знак приближения. Число справа, если его привести к десятичному виду не будет равно десятичному числу слева. Вот эта разница в представлении и дает ошибку в вычислениях.
И видим теперь, что Ваш текст «разошелся» с математикой.
Так где расхождение?
Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления?
Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.
Плевать на ошибку оцифровки в значительном числе случаев.
Полагаю, что этот акт надо оставить на усмотрение человека. А машина должна считать точно.Andy_U
13.09.2016 22:34Так где расхождение?
Так вы же сами совершенно правильно ответили:
Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.
… только теперь надо бы заголовок статьи поменять?
Innotor
13.09.2016 23:23Я не понял, по названной нами причине ошибки при вычислениях имеются или нет? Я же про ошибки говорю, которые обусловлены несоизмеримостью систем счисления. Так что неверно в названии?
Andy_U
13.09.2016 23:40Это не ошибки, а погрешности. Т.е. не bug, а feature. И Вы правильно сейчас написали, что причина в невозможности точно преобразовать числа конечной длины из N-ричной системы счисления в M-ричную. Т.е. пока мы используем лишь одну систему счисления, проблемы нет. Но Ваша статья называется «Фатальные ошибки двоичной арифметики...».
Innotor
13.09.2016 23:54-1Вы напрасно поставили многоточие. Речь идет именно об ошибках (или погрешностях) при двоичных вычислениях с десятичными ЧПТ. Не буду спорить по поводу семантики понятий bug и feature, но на мой взгляд последствия очевидны, как в том, так и в другом случае.
Andy_U
14.09.2016 00:23+1Вы напрасно поставили многоточие.
Не напрасно. В заголовке Вашей статьи (Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой) нет упоминаний про десятичную систему счисления.
Речь идет именно об ошибках (или погрешностях) при двоичных вычислениях с десятичными ЧПТ.
Ну нет внутри современных компьютеров (ну уровне железа) десятичных чисел с плавающей запятой. Только двоичные.Innotor
14.09.2016 06:58Да, я с Вами согласен. Надо было уточнить про десятичные числа. Боюсь уже поздно.
olgerdovich
13.09.2016 20:14анекдот в том, что как раз скорость света (в м/с ) является целым числом (точно 299792458), через которое определяется метр.
Siemargl
13.09.2016 11:12+1Дело не только в формате представления чисел.
Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе»
Во вторых, на самом деле FPU считает «не совсем по стандарту». В тоже же библиотеке, например GCC, масса воркэраундов. AFAIK -ffast-math будет выполнять расчеты только инструкциями FPU, и результат может вас удивить.Innotor
13.09.2016 11:45Да, на выходе FPU происходит усечение двоичного представления числа. Но именно усечение двоичного числа и приводит к появлению хвоста, значение которого априори определить не представляется возможным. А за счет нормализации часть хвоста оказывается в области ожидаемых верных цифр.
Siemargl
13.09.2016 13:40Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе».
Да похоже я слегка загнул — внутренние вычисления происходят в x87 в 80-битном виде. Т.е операнды расширяются к 80-бит, выполняются вычисления и после этого происходит обратная конверсия с понижением точности.
Если же используются SSE/AVX то там максимум 64-бит числа с плавающей точкой.
Утверждается (например на stackoverflow), что с предварительным расширением до 80-бит и без него можно получить разные результаты вычислений.Innotor
13.09.2016 15:30Если мне мою карму не опустят ниже плинтуса, в следующем топике я постараюсь представить свое видение этого вопроса. А здесь еще раз повторюсь, расширение количества бит под двоичную мантиссу не помогает избежать ошибок вычисления. Это я попытался показать на двух форматах float и double.
boolivar
13.09.2016 11:22x87 все вычисления производит в 80-битном расширенном формате, и для double и для float
Innotor
13.09.2016 11:48И вот когда мы отбрасываем двоичный хвост, возвращаясь к double или float, тут и вырастает десятичный хвост, который все и портит.
napa3um
13.09.2016 12:27В общем случае «хвост» в FPU уже есть, просто на выходе мы получаем меньшую точность этого хвоста. Математики, которым критичен этот хвост, и так в курсе того, что компьютер может хранить лишь конечное множество всех возможных значений, в курсе мантисс и экспонент, и в курсе различий между численным и аналитическим решением уравнения. Выводы делайте сами!
Innotor
13.09.2016 12:45Ну, про точность хвоста, мы ничего не можем сказать, нам про него ничего не известно.
Математики, которым критичен этот хвост, и так в курсе того, что компьютер может хранить лишь конечное множество всех возможных значений, в курсе мантисс и экспонент, и в курсе различий между численным и аналитическим решением уравнения
.
Но, позвольте, а как пользователю относиться к тому, что мы получаем на выходе Excel? Математики может и в курсе, что некоторые числа могут получаться неточными. А как пользователю определить, когда можно доверять результату, а когда нет?napa3um
13.09.2016 12:49+2Позвольте, как мне прооперировать себя, не владея скальпелем? Срочно нужна статья о хирургии для обычных пользователей. Причём, написать её должен не хирург, а другой обычный пользователь, случайно отрезавший себе палец.
Innotor
13.09.2016 13:12Ваш юмор я оценил. Но как быть с расчетами?
napa3um
13.09.2016 13:47Считать, учитывать.
Innotor
13.09.2016 14:20А как учитывать, если из заявленной точности — 15 достоверных цифр после запятой для нормализованных чисел формата double, верными оказываются только 7?
napa3um
13.09.2016 14:23Озвучьте вашу версию ответа, пожалуйста.
Innotor
13.09.2016 14:57Думаю, ответ такой. Не использовать в расчетах с действительными (рациональными) числами двоичную арифметику в том виде, как она реализована в стандарте. Причем не важно, как она реализована, на аппаратном или на программном уровне. Вся беда в том, что проблемы начинаются, когда десятичное число содержит дробную часть. Целые числа не приводят к ошибкам представления. Поэтому, если представлять числа в виде ЧПТ с целочисленной мантиссой, эти проблемы не возникают. Возникают другие проблемы, но они не системные и преодолимы. Попытка ответить на ваш вопрос была предпринята в моем топике https://habrahabr.ru/post/272251/. В комментариях можно найти ссылку на PDF-файл.
napa3um
13.09.2016 15:35Теперь стало понятно, где у вас «болит» (ранее эту статью не читал). Думаю, вам стОит не евангелизированием своих идей заниматься, а реализовать вашу альтернативу в виде библиотеки и сравнить её на каких-нибудь бенчмарках (поиск ранга матриц размером триллиард на триллиард, рассчёт какой-нибудь несущей конструкции с триллиардом элементов и т.п. — и по скорости, и по точности, и по стабильности этой точности) с существующими библиотеками и решениями. Мне кажется, что среди этих альтернативных решений вы и для себя откроете множество интересных подходов к контролю точности вычислительных задач.
Innotor
13.09.2016 13:10Вот программка, которую по моей просьбе написал Дмитрий Пикулик.
/*
* gcc -O0 -Wall -msoft-float -o test test.c
*/
#include <stdio.h>
#include <assert.h>
#include <float.h>
#include <math.h>
static double f(double a, double b)
{
double r = a — b;
return r;
}
int main (int argc, char **argv)
{
double a = 0.66;
double b = 0.659999996423721;
x = f(a, b);
printf(«x=%.40f\n», x);
return 0;
}
x=0.00000000357627905067658 95770513452589512 ?0.00000000357627905067659
Сравните с тем, что получится на калькуляторе.Tujh
13.09.2016 13:28+2Задайте себе вопрос, почему, при математических расчётах ЧПТ на ПК сравнение выполняют не так:
double a = f1(), b = f2();
а так:
if ( a != b ) {… }
double a = f1(), b = f2();
и на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?
if ( fabs( a — b ) > EPSILON ) {… }
Всё известно, кто хочет точности — пользуется специальными библиотеками.Innotor
13.09.2016 14:16-1Разница между двумя числами double a = 0.66 и double b = 0.659999996423721 составляет 0,000000003576279. Вопрос, какое должно быть EPSILON, чтобы убедиться, что это число равно числу 0.00000000357627905067659
на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?
Ну, статья не про IEEE754, а об ошибках, которые получаются в результате арифметических операций при использовании двоичной арифметики для десятичных ЧПТ. Эти ошибки не совсем очевидны, так как в большом количестве случаев результаты вычислений будут верными при использовании стандарта. Но где гарантия, что мы не столкнемся именно с теми немалочисленными ситуациями, когда будут получены совершенно неприемлемые результаты.Tujh
13.09.2016 15:24+1Вопрос, какое должно быть EPSILON, чтобы убедиться, что это число равно числу 0.00000000357627905067659
cfloat / float.h
FLT_EPSILON 1E-5 or smaller
DBL_EPSILON 1E-9 or smaller
LDBL_EPSILON 1E-9 or smaller
EPSILON — Difference between 1 and the least value greater than 1 that is representable.
То есть даже для float это «всего лишь» 0,00001Innotor
13.09.2016 15:56По-моему кавычки в словах «всего лишь» надо убрать, т.к. 24 двоичных разряда мантиссы формата float обеспечивают представление десятичного числа с точностью до 7 знаков после запятой.
Tujh
13.09.2016 16:01Кавычки тут больше для привлечения внимания, допустимая точность для float не 7, а пять знаков после запятой, именно это и отражает постоянная FLT_EPSILON
Innotor
13.09.2016 16:29Если мы говорим о представлении десятичного числа в двоичном коде, то 24 двоичных разряда мантиссы нормализованного числа позволяют представить любое десятичное число с 7 верными цифрами. Почему же вы ограничились 5 знаками?
Tujh
14.09.2016 08:17Если грубо — 5 знаков это допустимая точность вычислений для float, 9 — double. Любые вычисления, требующие большей точности требуют смены типа переменных. Например, если мы считаем некоторую величину где будут учитываться 5 и 6 знаки после запятой — float уже не подходит, его точности не хватает, выбираем double. Если нужна точность более 9 знаков — дабл так же не подходит. Это основы программирования с ЧПТ, учат ещё когда проходят эти числа.
Вы сейчас боретесь или с ветряными мельницами.Innotor
14.09.2016 09:31Посчитайте в формате float, чему это равно: 1234,567-1234,56=?
У меня получилось 0.0069580Tujh
14.09.2016 10:00#include <cmath> #include <cfloat> #include <iostream> int main() { float a = 1234.567, b = 1234.56; float c = a - b; float real_c = 0.007; std::cout << "1234.567 - 1234.56 = " << c << std::endl; // 1234.567 - 1234.56 = 0.00695801 std::cout << "c - \"real c\" = " << ( real_c - c ) << std::endl; // c - "real c" = 4.19924e-05 return 0; }
при замене на double:
// 1234.567 - 1234.56 = 0.007 // c - "real c" = -6.18455e-14
Так и не понял что Вы хотите этим доказать?
О «точности» float все знают.rafuck
14.09.2016 10:14Да это уже на троллинг со стороны топикстартера похоже, если честно. Когда вы записали 1234.567 во float, вы уже ошиблись в 5 десятичном знаке после запятой. То же самое с 1234.56.
Innotor
14.09.2016 10:41Так и я о том же. Как только вы преобразовали десятичное число к двоичному, вы ошиблись не в 5 знаке, а в 10000… знаке. Так как получили бесконечную дробь.
rafuck
14.09.2016 10:42В пятом десятичном знаке.
Innotor
14.09.2016 11:16В самом большом числе из приведенных всего 3 знака после запятой (1234.567). А вот после конвертации оно становится
1234.56710 = 1.0011010010.100100010010011011101001?•2?^10
=1234.5670166015625
Так про какой 5 знак идет речь?rafuck
14.09.2016 11:29Когда вы записали 1234.567 во float, вы уже ошиблись в 5 десятичном знаке после запятой.
rafuck
14.09.2016 11:33Остальные я не считал: 7->(4->8->6->2->4). И это неважно. При ошибке в пятом десятичном знаке после запятой мы имеем 8 правильных десятичных знаков в представлении исходного числа. Большего мы от float и не ждем.
Innotor
14.09.2016 11:45Ошибся не я, а природа, которая все это устроила. Еще раз повторюсь, речь идет не о точности конвертации чисел из одной системы счисления в другую, а о результатах вычислений.
rafuck
14.09.2016 10:071) И какова относительная погрешность?
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.
3) Какова относительная погрешность для чисел, которые вы в реальности записали в память?Innotor
14.09.2016 10:451) И какова относительная погрешность?
Если говорить о погрешности представления — разная для разных чисел
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.
Я все время именно о таких числах и веду речь. Представимые числа — точные и с ними проблем нет до тех пор пока в результате арифметических действий они не превращаются в приблизительные.
rafuck
13.09.2016 18:46+1Не совсем так. Скорее, так:
|a-b| < epsilon*max(|a|, |b|)
Tujh
14.09.2016 08:21Спасибо, не знал этого способа.
rafuck
14.09.2016 08:54Смыл в том что расстояние между соседними машинными числами в формате IEEE754 зависит от их абсолютной величины. Например, следующее за 10^8 во float будет 10^8+8. Поэтому абсолютная точность, скажем, 0.1 при таких порядках будет в принципе недостижима.
Refridgerator
14.09.2016 09:42в FPU есть флаг точности с возможными режимами:
00 – 24 бита мантисса, 7 бит порядок;
10 – 53 бита мантисса, 10 бит порядок (двойная точность);
11 – 64 бита мантисса, 15 бит порядок (расширенная точность).Innotor
14.09.2016 09:42И что из этого следует?
Refridgerator
14.09.2016 10:08Это было уточнение комментария «x87 все вычисления производит в 80-битном расширенном формате, и для double и для float».
Из этого следует, что при выполнении вычислений на FPU вы будете получать разные ответы в зависимости от того, в каком режиме точности он работает. Более того, вы будете получать различные результаты и в зависимости от того, как именно компилятор оптимизирует ваш код и в каком конкретно формате он хранит промежуточные вычисления.
Стек FPU ограничен восемью регистрами. Пока остаются свободные регистры для хранения промежуточных данных, очевидно, их точность остаётся неизменной. Но как только свободные регистры заканчиваются, компилятор вынужден сохранять их во внешней памяти — и обычно это делается в 64-х битном формате, с потерей 80-битной точности (как показало изучение ассемблерного кода от компиляторов Intel и Microsoft).boolivar
14.09.2016 12:53Эти настройки точности больше для того, чтобы результаты не были точнее чем вы сами того ожидали, проверяя на вычислениях ограниченной точности (взято отсюда https://en.wikipedia.org/wiki/X87#Description):
As this may sometimes be problematic for some semi-numerical calculations written to assume double precision for correct operation, to avoid such problems, the x87 can be configured via a special configuration/status register to automatically round to single or double precision after each operation
Когда вы работаете на сопроцессоре, вы накапливаете результат в одном регистре расширенной точности, другие вам для этого не нужны.
vadimr
13.09.2016 15:06Машинные операции с вещественными числами имеют ограниченную точность, что Вы и продемонстрировали на примерах.
В эксплуатационной документации к первым ЭВМ так и писали, что машина получает не сам результат математической операции, а доступное приближение к нему. Потом как-то все привыкли, и писать перестали.Innotor
13.09.2016 15:14-1Используя тот или иной инструмент, я должен знать, какую точность гарантирует этот инструмент. Если для double гарантирована точность до 15 десятичных знаков после запятой для нормализованных чисел, а по факту я имею всего 7, то как можно использовать этот инструмент?
vadimr
13.09.2016 15:22В каком примере вы имеете 7?
Innotor
13.09.2016 15:50См. в статье, где приведен пример для вычитания чисел в формате double.
Tujh
13.09.2016 15:59+10,789012345005176
Погрешность меньше допустимой 1Е-9, так что всё укладывается в рамки допустимого.
А мы ожидали получить число: 0, 789012345
vadimr
13.09.2016 16:24Там нет примера, только Ваши утверждения.
#include <stdio.h>
int main () {
double d;
d = 105.3256-105.32;
printf («d = %lf\n», d);
return 0;
}
d = 0.005600
Если Эксель вам врёт, то причём тут машинная арифметика? Он вообще с символьными данными работает.Innotor
13.09.2016 16:51Посмотрите в комментариях чуть выше. Там приведен пример для формата double.
vadimr
13.09.2016 16:55Вы можете конкретно написать пример? Из Ваших реплик ничего не понятно.
Innotor
13.09.2016 17:03Не могу, хабр. ограничивает ресурсы. Пишите в личку.
vadimr
13.09.2016 17:06Если Вы имеете в виду свой комментарий “Вот программка, которую по моей просьбе написал Дмитрий Пикулик”, то в этой программе:
1) отсутствует определение переменной x, поэтому она не может быть скомпилирована;
2) после вставки определения “double x;”, результат, выдаваемый программой, соответствует теоретическому с точностью до 16 десятичных цифр, что отвечает точности формата double.Innotor
13.09.2016 18:12Посчитайте разность: 123456,789012345-123456.
vadimr
13.09.2016 18:420.789012345005176, разница в 18-м десятичном знаке.
Innotor
13.09.2016 19:40123456,789012345-123456=0,789012345. Согласно вычислениям на калькуляторе виндоуз.
vadimr
14.09.2016 07:24Ну берите какой-нибудь язык PL/I и тип decimal fixed, если вас интересует точное представление десятичных чисел. Как верно указано в соседней ветке, настоящие физические параметры не имеют точного представления ни в одной системе счисления, поэтому совершенно неважно, как их записывать. Главное, чтобы погрешность алгоритма соответствовала требованиям решаемой задачи.
Innotor
14.09.2016 09:15Вопрос в цене-времени-сложности. Нет таких калькуляторов, которые работали бы на указанных вами принципах.
vadimr
14.09.2016 09:20Каких именно принципах? Практика программирования показала, что точное представление десятичных чисел никому не нужно, кроме бухгалтеров, которым достаточно целых копеек. Поэтому от десятичных дробей в современных широко распространённых системах программирования отказались. Остальные вещественные числа – это приближения иррациональных значений (так как мощность множества иррациональных чисел бесконечно больше мощности множества рациональных, то вероятность точно попасть в рациональное значение равна нулю).
vadimr
14.09.2016 09:28Причём, так как изначально практически все источники вещественных цифровых значений в современном мире имеют двоичную природу, то представление чисел в десятичной системе их только портит.
Рано или поздно человечество перейдёт к шестнадцатиричной в быту.
vadimr
13.09.2016 16:32a = 123456.789012345-123456.0;
printf («a = %.15lf\n», a);
a = 0.789012345005176
Кто вам сказал, что Эксель работает с числами в формате double и в соответствии с IEEE 754?Innotor
17.09.2016 08:33Microsoft Excel was designed around the IEEE 754 specification to determine how it stores and calculates floating-point numbers.
Эксель в статье был использован как инструмент для иллюстрации правильности приведенных вычислений в стандарте IEEE754.
Запрограммируйте приведенный вами пример на любом языке, в котором используется формат IEEE754, задав формат дабл, и вы получите тот же результат. Так что эксель здесь не причем.vadimr
17.09.2016 09:10Тут не написано, что операции с числами в Экселе соответствуют стандарту IEEE 754. Написано, что Эксель спроектирован где-то вокруг этого стандарта (даже не знаю, как корректно перевести это бессмысленное маркетинговое утверждение).
Я свои примеры даю на языке Си с использованием арифметики IEEE 754.
LynXzp
13.09.2016 16:27Курс дзена молодого бойца: попробуйте перемножить, а тем более уж поделить Точно одно-двух байтные целые числа на AVR.
Innotor
13.09.2016 17:06Мастер! Попробуйте с точностью до 6 знака после запятой найти частное 2/3 в двух разрядном калькуляторе.
LynXzp
13.09.2016 17:09Я никого не хотел обижать. Просто AVR может ошибаться уже в 4-х значных числах, и без запятой.
boolivar
13.09.2016 22:44Простите, но при чем тут AVR? Да, там нет сопроцессора, поэтому все операции с плавающей запятой производятся программно, это может сделать сам компилятор или использовать библиотечные реализации (http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_math).
Однако, вы можете реализовать все операции самостоятельно, с любой точностью, какой пожелаете, ограничиваясь объемом памяти кристалла, конечно же.LynXzp
13.09.2016 23:18На самом деле да, все как Вы сказали, я имел в виду скорее компилятор. Хотя не могу сказать по всем, но avr-gcc (самый популярный) и icc (очень редкий) генерируют неточный код. И у меня даже есть маленькая функция для быстрого и более точного деления на 10 (каждый раз при отображении чисел нужна).
Но и это не все, там есть аппаратная функция FMUL которая умножает два байта. И она может ошибаться на 1-2 значения.
P.S. еще раз: я про целые числа, не про числа с плавающей запятой. Если даже в операциях с целыми числами могут быть такие ошибки вычислений, то что уж говорить про числа с плавающей запятой. И стоит добавить что у чисел с плавающей запятой неточность ожидаемая, то неточность при использовании целых чисел может застать в расплох. И да, я про переполнения конечно же.boolivar
14.09.2016 11:01Как можно ошибиться при умножении? Вы о чем вообще? Команда FMUL предназначена для операций с фиксированной запятой она сдвигает результат влево чтобы запятая результата оказалась на том же месте, что и у входных аргументов.
Это значит, что если попытаться с её помощью перемножить два целых числа вы получите число в два раза большее чем ожидали.LynXzp
15.09.2016 01:19+1Ныряю в пепел, стыд и позор. Про FMUL я просто слышал и не правильно понял, признаю (я С-шник). Но и в остальном я не могу воспроизвести ничего. Даже ошибку в делении. Заминусуйте начальные комментарии чтобы никто ересь не читал. С чего я это все взял — не знаю, может раньше так было, а может у меня повреждение памяти, жаль с ECC нельзя поставить.
Innotor
13.09.2016 23:15Для получения точного результата, при умножении целых беззнаковых чисел, необходимо, чтобы результирующий регистр имел число разрядов не менее суммы разрядов сомножителей.
LynXzp
13.09.2016 23:22Я не про переполнения конечно, иначе этот диалог, начатый мной, не имел бы смысла. Сходу не найду, но вот например есть калькулятор fmul calc — позволяющий смотреть где ошибается микроконтроллер avr при использовании этой инструкции (аппаратная инструция целочисленного умножения, принимает два байта, результат записывается в двухбайтовую переменную). Не знаю на сколько точен калькулятор, но эти ошибки имеют место быть.
VaalKIA
14.09.2016 02:46+2Что автор-то хочет, мне — не понятно…
По моему, у него, каша в голове: Excel, какие-то листинги программы, обещания 7 знаков после запятой наследственной грамотой…
Вы сами-то понимаете о чём речь?
Если вы показываете листинг программы, где написанна десятичная константа, то она не десятичная, это всего лишь соглашение, точно так же как кодировка листинга (может быть в utf8, но это не значит, что print(«hello world») небудет в ANSI) и вывода программы. Если в листинге написано a := 0.6, почему вырешили, что в компьютере материализуется довоичнодесятичный аппаратный блок арифметики, а не разумное: «пишите как удобно, компилятор сам преобразует»?
Значит вычёркивайте вообще все слова «десятичные» из текста.
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков? Теория говорит, что будет уже 4 точных знака и не надо делать выпученные глаза и тыкать нас в это носом, идите и читайте книжки.Innotor
14.09.2016 07:40Уважаемый! Нельзя ночью писать коменты, Судя по всему, при изложении своих мыслей, вы пользуетесь принципом «пишите как удобно, компилятор сам преобразует».
Если по существу,
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков?
Если следовать вашим знаниям, то в формате float, после 7 шагов суммирования, мы уже имеем совершенно неверный результат, даже, если складывались точные числа. Ну или 16 шагов для дабл.VaalKIA
14.09.2016 14:02Когда говорят о точости, делают оценку сверху. Так что ваш ответ зависит от того, как считать, вы же не будете спорить с тем, что мы можем только приближённо прогнозировать худший вариант, а что будет на самом деле надо уже смотреть.
Если в кратце, то замените разряд на X и подумайте сами, способен ли он после сложения повлиять на старшие знаки?
1.0X+1.1X= это может быть, например, так 1.09+1.19 =2.28 а не 2.1X, точность — упала. После 7 шагов она упадёт ещё больше, но оценка сверху по частям и общая оценка сверху будут разниться, так же как и в бухгалтерии сумма НДС товаров не равна НДС от суммы товаров.
Проще говоря, если вам числа даны с определённой точностью, суммирование будет накапливать погршеность, умножение её преумножать, на каждой итерации всё больше разрядов могут отличаться и на целую часть это тоже распространяется. Есть попытки бороться с этим в финансах внося стохастическую составляющую. но по факту смысл не меняется и совсем от этого никуда не деться, можно лишь немного изменять масштабы бедствия в конкретных практических случаях.
Siemargl
14.09.2016 17:52Я бы в подобный случаях советовал использовать hexfloat (%a) формат, чтобы различить, где у вас исходная десятичная константа уже с округлением.
cout << 0x0.123p-1 << endl; cout << std::hexfloat << 6.6;
mmMike
14.09.2016 06:37Может я не прав, но тем кто про эти очевидные вещи знает — статья не очень интересна.
А тем, для кого это "открытие" лучше было бы еще привести практические рекомендации (и примеры придумать/привести) типа :
- Никогда не сравнивай результат вычисления double/float типа с константой на равенство.
- Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым.
3… и т.д.
впрочем, хозяин барин...
Innotor
15.09.2016 08:20Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым
.
Это, если использовать медицинские аналогии, которые здесь приводил один пользователь, все равно, что сказать: " Я вам даю таблетку от головной боли, но последствия непредсказуемы".mmMike
15.09.2016 08:59+1Вообще то с таблеткой обычно дают рекомендации по использованию. Если уж пошли такие удаленные аналогии.
А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Причем эта тема весьма стара и классическая. Начиная с FORTRAN 66, когда программированием начали заниматься люди, не вникающие (да не надо им это) как выполняется, например, работа с числами с плавающей точкой на разных архитектурах и пр.
Да самое элементарное (одно из..) — выравнивание (умножение + деление результата на константу) там где нужно исходя из задачи и предполагаемой размерности чисел.
Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?
Innotor
15.09.2016 09:51А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Ну да, как у Макаренко в «Республике ШКИД» — «Теоретически она лошадь, а практически она падает» Вы действительно считаете, что все проблемы с обработкой ЧПТ решены? А зачем INTEL в железе реализует обработку ЧПТ в двоично-десятичном формате?
Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?
Чуть ваше я как раз объяснял вкратце с каким форматом работает калькулятор. Так что, кто не знал, видимо, уже знает.mmMike
15.09.2016 09:59Вы действительно считаете, что все проблемы с обработкой ЧПТ решены?
С чего Вы вдруг завелись?
Все что я сказал изначально, что желательно было бы привести примеры типичных классических ошибок программиста при работе данными в формате с "плавающей точкой" и как их избежать.
И все…
Все остальное — это Вы уже сами за меня додумывает и высказываете.Innotor
15.09.2016 10:14А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Вы считаете, что этой куче чего то не хватает? и поэтому
привести примеры типичных классических ошибок программиста при работе данными в формате с «плавающей точкой» и как их избежать
Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.
Извините, но просто поговорить, действительно не интересно.mmMike
15.09.2016 10:29Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики.
Я вначале подумал, что это такая тонкая шутка, как вступление к лекции "для чайников". Но Ваш последний комментарий показал, что это не было шуткой, а сказано на полном серьезе:
Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.
Вы действительно считаете, что изложили в статье что то очень оригинальное и никому не известное?
Тогда "Извините, но просто поговорить, действительно не интересно.".
Ну конспекты лекций я сканировать и отправлять не буду (если найду их еще).
А так google: "проблемы с плавающей запятой учебник".
Refridgerator
15.09.2016 10:44Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена
Первая же ссылка в google на запрос «floating point issues»:
What Every Computer Scientist Should Know About Floating-Point ArithmeticInnotor
15.09.2016 16:14К сожалению формат дискуссии не позволяет сделать подробный анализ выкладок в указанной вами статье. Но, если вы с ней знакомы, то, во-первых, в ней рассматривается частный случай:
«A relative error of b — 1 in the expression x — y occurs when x = 1.00...0 and y = .rrr..., where r = b — 1» Обозначения изменены, чтобы не вставлять картинки. Далее, доказывается справедливость этого высказывания и, чтобы избежать этой ситуации автор предлагает ввести защитную цифру, которая позволяет получить результат близкий к factor times e. Который характеризует ошибку вычислений и определяется как e=(b/2)b^(-p). Далее доказывается, что добавление защитной цифры делает относительную погрешность не хуже 2e.
Возвращаясь к моей статье. Для формата float: b=2, p=24, a=105.3256, b=105.32 e=0,00000006
a-b=0,000000024. Относительная ошибка 0,000000024/0,0056=0.0000043, что существенно больше 2e=0,00000012vadimr
15.09.2016 16:38Чисто формально, абсолютная погрешность разности равна сумме абсолютных погрешностей уменьшаемого и вычитаемого, и это никак не связано со способом вычислений и представления чисел. Относительная погрешность может получиться какая угодно, в зависимости от соотношения порядков результата и операндов.
Innotor
15.09.2016 17:34У нас два точных числа a=105.3256 и b=105.32. Какова их абсолютная и относительная погрешность? Она равна нулю.Преобразуем их в двоичные числа и получаем приблизительный эквивалент точных значений. Чем меньше длинна двоичной мантиссы, тем менее точным становится десятичный эквивалент двоичного числа. При арифметических действиях над приблизительными числами всегда получаются приблизительные результаты. Абсолютная разность между полученным точным результатом и полученным приблизительным результатом дает абсолютную ошибку. Отношение абсолютной ошибки к точному числу дает относительную ошибку.
Относительная погрешность может получиться какая угодно, в зависимости от соотношения порядков результата и операндов
О каких соотношениях порядков результата и операндов идет речь? Желательно с примерами.vadimr
15.09.2016 20:18Если уж мы говорим о погрешностях, давайте изъясняться метрологически корректно. Может быть, в идеальном платоновском мире и существуют точные числа, но в реальности каждое значение имеет погрешность, связанную с различными причинами. В частности, всякое число, записанное в позиционной системе счисления, имеет погрешность представления, равную половине цены младшего разряда. Для числа 105.3256 абсолютная погрешность представления равна 0.00005, для числа 105.32 – 0.005. Разность этих чисел равняется 0.01 с погрешностью 0.00505, равной сумме погрешностей операндов.
Предполагая, что вместо второго числа Вы подразумевали 105.3200, можно заметить, что его абсолютная погрешность равна 0.00005, а разность чисел равна 0.0056 с абсолютной погрешностью 0.0001. Так как сама величина разности мала по абсолютной величине, то относительная погрешность разности оказывается велика, 0.0001/0.0056 = 0.0179.
Заметьте, это всё чисто аналитические выкладки, не имеющие пока никакого отношения к компьютеру и его представлению вещественных чисел.Innotor
15.09.2016 20:42Может быть, в идеальном платоновском мире и существуют точные числа, но в реальности каждое значение имеет погрешность, связанную с различными причинами
Берем 5 яблок и отнимаем от них 2. Получаем 3? Или все же 3+- немножко? Какой ответ верен? Яблоки могут быть совершенно разные по размерам. Если мы считаем в столбик разность двух чисел 105.3256 и 105.32 мы получаем в ответе 0.0006. Или +- что то еще? Калькулятор не знает предыстории чисел с которыми имеет дело. Это вы должны интерпретировать полученные результаты.vadimr
15.09.2016 20:503 плюс-минус немножко, конечно. Пока вы отнимаете яблоки, они истираются, или, наоборот, что-нибудь с рук на них налипает. Проще заметить, вычитая 2 тонны яблок из 5 тонн.
(Говоря о единичных яблоках, это, в практических целях, 5.0 и 2.0. Половину яблока ещё различаем при подсчёте количества, а 0.05 – уже нет.).
То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно. Инженеров, как правило, отучают вычитать 105.32 из 105.3256 ещё на первом курсе.Innotor
15.09.2016 21:06Пока вы отнимаете яблоки, они истираются, или, наоборот, что-нибудь с рук на них налипает.
Если какое-нибудь яблоко не прилипнет к рукам раздающего, они будут разложены ровно на две кучки по 5 и 3 в каждой.
То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно
Ну и слава Богу. Не его это калькуляторное дело интерпретировать результаты вычислений.
Вы почему-то хотите узурпировать всю вычислительную технику под метрологические задачи. Но ведь есть еще всевозможные задачи: математического моделирования, криптография. И, в конце концов, не лишайте человечество узнать значение числа пи в N-том знаке после запятой:).vadimr
15.09.2016 21:07Я не возражаю, но тогда вообще не надо говорить про погрешности.
Innotor
15.09.2016 21:15Я бы про них и не говорил, если бы они не возникали в процессе вычислений дробных десятичных чисел инстументарием двоичной арифметики.
vadimr
15.09.2016 21:18Так вы сначала в аналитической теории разберитесь, что и как вы хотите посчитать. А если вы считаете конечную запись десятичного числа за абсолютно точное значение, то дальше уже из этой ложной предпосылки можно сделать какие угодно выводы.
Innotor
15.09.2016 22:13У меня 3.00 детей в десятичном исчислении. Это истина. Это конечная запись, больше детей у меня не будет. Какие ложные предпосылки вы в этом усматриваете и какие выводы вы можете сделать? Вариант — дети не от меня, исключается:)
А если серьезно, то точные числа существуют, а натуральные числа вообще связаны исключительно со счетными задачами. Это потом появились рациональные числа, в результате операций сравнения. Что больше (меньше) и во сколько раз. И даже для них, если числа соизмеримы, т.е. в них укладывается ровное количество меры, они будут точными. Не в смысле точности измерения, а в смысле количества мер, которые в них укладывается. Пример: эталон — человек обыкновенный. Сколько человек в кинотеатре? Скажем — 201. Это точное число или приблизительное?
Вообще, понятий точности много. Большой энциклопедический политехнический словарь понятие точности определяет как «степень приближения истинного значения рассматриваемого процесса, вещества, предмета к его теоретическому номинальному значению» [ dic.academic.ru/dic.nsf/polytechnic/9524/ТОЧНОСТЬ ]. Для численных задач, мне кажется, это верное определение. Поэтому результат, полученный в вычислительном устройстве, тем точнее, чем он ближе к вычисленному столбиком вручную.
Извините, если сделаете свой комент, скорее всего отвечу послезавтра. Уезжаю.vadimr
16.09.2016 05:49Когда вы говорите о своих детях, то это их нумерация натуральными числами в вашем списке детей, а не измерение количества, поэтому в данном случае говорить о погрешности не приходится.
Что касается точности, то в инженерном деле стараются не использовать этот термин, если стремятся к строгости терминологии. Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.Innotor
17.09.2016 09:05Когда вы говорите о своих детях, то это их нумерация натуральными числами в вашем списке детей, а не измерение количества, поэтому в данном случае говорить о погрешности не приходится.
Считаю, все ли дети дома? Один, два, три. Так, общее количество — приблизительно 3!
Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.
Не качественно, а количественно. В формате флоат можно представить точно до 7 значащих десятичных цифр. В формате дабл — до 16 десятичных цифр. И хотя, после перевода в десятичное представление дробного двоичного числа конечной длительности, вы можете получить бесконечную дробь, первые 16 значащих цифр десятичного числа, проконвертированного из 2 в 10 систему, будут точными.
DistortNeo
15.09.2016 21:52Не путайте тёплое с мягким.
5 — число целое, хранится в целочисленном типе.
Хотите точные операции с дробями — используйте fixed point арифметику.Innotor
15.09.2016 22:20Хотите точные операции с дробями — используйте fixed point арифметику.
Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой? Зачем тогда ЧПТ?DistortNeo
15.09.2016 22:47Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой?
Зачем тогда ЧПТ?
Fixed point имеет фиксированную абсолютную точность, тогда как Floating point — фиксированную относительную точность.
64-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.Innotor
17.09.2016 08:4664-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.
Во-первых, 64-битное целое двоичное число может обеспечить представление десятичного числа с точностью не выше 16 значащих цифр. А во-вторых, в целом числе отсутствует дробная часть, которая и приводит к описанным в статье неприятностям.
Refridgerator
16.09.2016 05:45Хотите точные операции с дробями — используйте fixed point арифметику.
Для точных рациональных вычислений нужно использовать специальные средства, например, класс «ratio» в c++, который хранит отдельно числитель и знаменатель в целочисленном виде.
mmMike
15.09.2016 09:07До сих пор помню, как мне гад препод, из вредности, срезал 5 (на 4) вопросом про разрядность регистра 80487 (FPU сопроцессор, если кто не помнит). На лекции его не ходил. Хотя каждый день на кафедре встречались.
Одна 4-ка за 4 года.
Базовые знание по работе комп. потрохов очень помогают. Меньше ситуаций "Это магия какая то… почему результат не верный".
VaalKIA
15.09.2016 13:07Я наконец-то понял, что хотел сказать автор.
Сначала несколько пунктов:
1. Никакие FP не имеют к сути статьи никакого отношения.
2. Количество значащих разрядов и точность чисел тут тоже не при чём.
Заголовок статьи должен был быть таким: «Фатальные ошибки двоичного представления дробных десятичных чисел.»
Суть такова: мы бухгалтеры и считаем денюшку, у нас есть десятичне числа с копейками, к примеру.
Есть факт, что конечные десятичные дроби при переводе в двоичные дроби, могут порождать бесконечные. В переводе на русский язык некоторые копейки в компьютере не могут быть представленными точно и будут записаны округлённой двоичной дробью. Округление, это зафиксированная погрешность, она после множества вычислений будет аккумулироваться и нарастать.
В итоге, результат вычислений на бумжке в столбик и с помощью программы на компьютере может не совпадать в копейках. что для бухгалтера очень неудобно.
Решение элементарно: двоично-десятичное представление полностью устраняет проблему.
Если я не угадал, то — сделайте мне лоботомию, я больше не хочу быть телепатом.DistortNeo
15.09.2016 15:16+1Головная боль бухгалтера — как разделить миллион рублей поровну на трёх человек.
Здесь даже точные десятичные вычисления не помогут.Innotor
15.09.2016 18:23Я конечно далек от бухгалтерии, но может такое наивное решение пригодится. Создать графу «нераспределенные остатки» в которой учитывать копейки или доли копеек. Эти деньги временно хранить в банке. Когда следующий раз будете распределять миллион прибавьте к распределяемой сумме нераспределенные ранее остатки глядишь сумма и разделится. Если нет — опять остатки в банк.
Refridgerator
15.09.2016 18:48Раз об этом зашла речь, а к чему вы, собственно, близки? Если вы далеки от бухгалтерии, математики и программирования, но при этом испытываете потребность решать фундаментальные ограничения чисел с плавающей точкой.
Innotor
15.09.2016 19:25+1Ответ на этот вопрос как то прольет свет, на рассматриваемую проблему?
Refridgerator
15.09.2016 20:21Прольёт свет на контекст.
Innotor
15.09.2016 20:50Я так понимаю, к контексту у вас вопросов нет? Есть вопросы к социальному статусу автора? В зависимости от ответа на последний вопрос, вы согласитесь с контекстом или нет?
Кстати, а что по поводу What Every Computer Scientist Should Know About Floating-Point Arithmetic? Я что-то не правильно посчитал?Refridgerator
15.09.2016 21:46Нет вопросов к социальному статусу автора.
Нет фатальной ошибки в том, что формат с ограниченной точностью имеет ограниченную точность.
Если вы хотите убедить, что в реализации IEEE754 есть ошибки — то надо не сюда писать, а в комитет по стандартизации.
Если вы изобрели новую, улучшенную технологию обработки чисел с плавающей точкой — то её надо срочно патентовать, пока этого не сделал кто-то другой.
Если вы боитесь, что «такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой», то в любом случае уже поздно что-то менять — в современных процессорах стандарт реализован аппаратно.
Andy_U
16.09.2016 12:06Насколько я понимаю, в бухгалтерии имеются однозначные жесткие правила округления до копеек. Типа, как посчитать 13% от 123 руб 10 копеек.
Guyverjoke
Думаю, стоит убрать капс из заголовка поста.
Innotor
Убрал