Pull to refresh

Comments 229

UFO just landed and posted this here
Так это всё общеизвестные факты.

Всё равно что написать статью «НЕВОЗМОЖНОСТЬ ПРЕДСТАВЛЕНИЯ ЦЕЛЫХ ОТРИЦАТЕЛЬНЫХ ЧИСЕЛ С ПОМОЩЬЮ ТЕКУЩЕЙ РЕАЛИЗАЦИИ ДВОИЧНОЙ СИСТЕМЫ СЧИСЛЕНИЯ В ЭЛЕКТРОННЫХ ВЫЧИСЛИТЕЛЬНЫХ МАШИНАХ.»
В статье речь идет о результатах арифметических вычислений действительных десятичных чисел с использованием двоичной арифметики.
Я просто по аналогии привёл пример.
В учебниках же пишут про float и double, что они не точные.

Ну а в качестве иллюстрации нагляднее такое поведение — просто преобразование одного и того же числа 0.18 в VBA:
из double в float
?csng(cdbl(0.18))
 0.18 

из float в double
?cdbl(csng(0.18))
 0.180000007152557 
В учебниках же пишут про float и double, что они не точные.

Да, это всем известно. Но в статье речь идет не об ошибках преобразования десятичных чисел в двоичные и обратно, а к последствиям, к которым эти преобразования приводят при простейших арифметических вычислениях.
Согласен, хотя в очень старом учебнике об этом тоже было написано, и была даже пара примеров.
К сожалению я не нашел этого очень старого учебника и поэтому ничего возразить не могу.
Ребятам даю пример:
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; 

Какое значение будет после окончания цикла? Или сколько итераций цикла будет выполнено?

Шок гарантируется. Потому хорошо запоминается, что вещественные числа не стоит проверять на равенство и неравенство.
Дайте ссылку, где эта проблема рассматривается в таком контексте.
1) Все ваши примеры эксплуатируют один и тот же эффект: не каждая конечная десятичная дробь является конечной дробью в двоичной системе счисления. Тут как бы на IEEE754 не стоит всех собак вешать. Скорее, следует сетовать на конечность представления чисел (каким бы оно не было).
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.
" Все ваши примеры эксплуатируют один и тот же эффект: не каждая конечная десятичная дробь является конечной дробью в двоичной системе счисления. Тут как бы на IEEE754 не стоит всех собак вешать. Скорее, следует сетовать на конечность представления чисел (каким бы оно не было)".


Вы совершенно правы. И про IEEE754 вы правы. Стандарт работает с теми числами, которые ему предлагают. Но как Excel понять, где правильное число, а где не правильное. Если в большинстве случаев на выходе правильный результат, а в некоторых, случайных, не правильный, как быть?
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.

Но почему же Excel использует неустойчивый алгоритм стандарта IEEE754 для сложения, вычитания и проч. Если на выходе мы имеем непредсказуемый результат?
Для скорости и экономии памяти конечно!
Ведь есть библиотеки для точных вычислений. Но они, естественно, медленнее чем железная реализация. И железная реализация сделана такой какая она есть ради скорости и экономии памяти.
Если Вас устраивает экономия памяти, при которой вы получаете результат с относительной погрешностью 0.6% на одной операции, то конечно проблем не будет.
0,0056-0,005599976 =0,005599976/105.3256 ≈0,006*100=0.6%
Если нужна точность — используют или фиксированную точку или готовые библиотеки или пишут что-то самодельное.
А можно подробнее откуда такое число 0.6% относительной погрешности?
(0,0056 — 0,005599976) / 0,0056 ≈ 0.000004 относительной погрешности. Нет?
Да, вы правы, в пылу полемики неправильно посчитал.
Вот смотрите, простой пример:
    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
Далее я делаю миллион вычислений по определенному алгоритму и должен быть уверен, что данная ошибка представления (да и все остальные ошибки, возникающие при каждом вычислении) в результате не накопятся и не превысят определенный порог.

В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.
Какой именно пример из приведенных вами доставляет такие страдания? Я просто не понимаю, что вы имеете в виду под «младшим разрядом» и как он соотносится с порядком самих чисел.

Вообще, я и не говорил, что формат, принятый для представления вещественных чисел, идеален. Да, можно придумать примеры, когда получаются неожиданные результаты. Да, иногда нужно знать, как все устроено, чтобы не наступать на грабли.
Какой именно пример из приведенных вами доставляет такие страдания?

Последний пример в статье, не то что доставляет страдания, но заставляет ощущать некий дискомфорт, когда тебя так нагло «обвешивают». В мантиссе, как в десятичной, так и в двоичной, имеются цифры, которые считают справа налево от первой значащей цифры справа. Вот ее я и называю младшим разрядом в мантиссе числа (не путать с машинной мантиссой). К порядку чисел это никакого отношения не имеет.
Да, можно придумать примеры, когда получаются неожиданные результаты.

А вы уверены, что такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой?
В последнем примере вы сравниваете расчеты в Excel с вычислениями на калькуляторе, при этом каждое действие на калькуляторе округляете до 15 знака после запятой. Так?
В итоге результат Excel и калькулятор совпадают в 15 знаках после запятой. Я все правильно понимаю?
Простите, может я слишком хочу спать и неправильно интерпретирую ваши 18 значные дроби.
Правильно. Если вычислять по правилам арифметики с точностью до 15 знака, с калькулятором мы делаем все верно. Если вычислять до 15 знака после запятой в формате дабл, и значащие цифры считать от точки в ненормализованном числе, то хвост не мешает. Арифметические операции не приближают неверные цифры к точке. Но, если считать по количеству значащих десятичных цифр, или от точки, в нормализованном числе, то хвост попадает в область верных цифр или участвует в образовании младшей значащей десятичной цифры. Проблема в том, что отсечение хвоста в двоичном коде увеличивает хвост в десятичном представлении и приближает неверные десятичные цифры к точке. Чтобы все было корректно, надо на каждом операционном шаге десятичное представления результата округлять до нужного значения
Вот честно, два раза перечитал ваш комментарий и ничего не понял. Вы сравниваете два результата вычислений. Один провели сами с точностью до 10^(-15), другой доверили экселю. Получили совпадение в 15 знаках после запятой. И вам почему-то это не нравится. А вот я бы, наверное, не доверял скорее результату ваших вычислений с арифметическими округлениями, а не округлением до четного.
В соответствие с правилами арифметических операций, чтобы сложить/умножить/разделить два числа с точностью до N-го знака после запятой, каждое число должно быть округлено до N знаков. Делая арифметические операции над двоичными числами, которые приблизительно представляют исходные десятичные, мы не можем выполнить этого требования, т.к. уменьшение разрядности двоичного числа делает его менее точным в его десятичном представлении. Таким образом, мы имеем нарушения правил сложения десятичных приблизительных чисел, в результате которого мы получаем неверный результат.
1.25/0.25 = 5
теперь, следуя вашей логике, я округляю до десятых, надеясь получить один правильный знак после запятой (N = 1)
1.3/0.3 = 4.(3)
Сколько верных знаков после запятой у меня получилось?
Я что-то неправильно округлил?
Да, по правилам арифметики так и получается. Как говорится «что выросло, то выросло»
Я цитирую вас:
В соответствие с правилами арифметических операций, чтобы сложить/умножить/разделить два числа с точностью до N-го знака после запятой, каждое число должно быть округлено до N знаков.

я вам показал только что, что это не так.
Так компьютер-то работает с двоичными числами. Вся проблема, в данном случае, оттого, что Вы зачем-то записываете двоичные, по своей технической природе, числа в приближённой десятичной форме.
К сожалению, если бы мы в жизни использовали шестнадцатиричную, восьмеричную или, на худой конец, двоичную систему счисления, проблем бы с компьютерными вычислениями не было. Но мы, как правило, оперируем с десятичными числами и пытаемся заставить компьютер их понимать. И получать информацию от него мы хотим в десятичном виде, как привыкли.
Зачем вам в жизни вещественные числа? В типичном случае, компьютер получает информацию от датчиков в двоичной форме, обрабатывает её в двоичной форме и выдаёт на исполнительные механизмы тоже в двоичной форме. А выдача человеку на экран носит чисто справочный характер.

В Вашем примере с ядерной установкой, десятичной системе просто неоткуда взяться.
Вся беда в том, что как только нам приходится делить два целых числа в десятичном или в двоичном коде, возникновение действительных, рациональных, дробных чисел неизбежно. Вопрос только в том, какую точность мы желаем получить при ограниченных аппаратных ресурсах.
Если бы мы использовали 16-ричную систему, то проблемы бы остались такими же.
Ну, проблемы неточности представления двоичных дробей не было бы.
Почему? Все равно округлять бы пришлось из-за ограничения на длину мантиссы.
Если бы в длину мантиссы по количеству знаков не уложились, то да. Но короткие шестнадцатиричные дроби кодируются точно, в отличие от некоторых десятичных, вроде 0.2.

Часть проблем автора поста связаны с тем, что не все конечные десятичные дроби имеют конечное двоичное представление.
16-ричные дробные числа, не переполняющие разрядную сетку мантиссы конечно могут быть точно представлены в двоичном коде. Это две соизмеримые системы счисления. Но, когда приходится делить два таких числа друг на друга мы опять можем получить бесконечную дробь:
0.9 ⁄ 0.7 = 1.49(249) в 16-ричной системе счисления.
Смотря что эти 0.2 означают. Если это именно точное значение, то логичнее его представлять как 1/5, а не 0.2.

Если же это результат физического измерения (масса, например), то точность измерения будет на порядки ниже точности округления. Если значение измерено с точностью до 1 знака, то мы можем смело 0.2 представить как 21/256, и ничего не потеряем.
В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.

А это уже вопрос используемого алгоритма и контроля погрешностей. В частности, Алгоритм Кэхэна как раз и используется для суммирования большого количества элементов без фатального накопления погрешности.
Алгоритм Кэхэна работает за счет привлечения еще одного операционного программного регистра в дополнение к аппаратному. Это все равно, что увеличить в два раза аппаратный регистр. При этом проблема неверных цифр («хвоста») не пропадает.
Это все равно, что увеличить в два раза аппаратный регистр.

Это как? По моей логике, «увеличить в два раза аппаратный регистр» = «увеличить точность в 2 раза», в то время как в алгоритме Кэхэна ничего подобного не наблюдается. Там просто контролируется накопление погрешности. Опять же, с ограниченной точностью.
А алгоритм Кэхэна для того и придуман, чтобы увеличить точность вычислений в 2 раза.
Ничего подобного, абсолютная точность алгоритма совпадает с точностью переменной, в которой копится сумма. Относительная погрешность лучше — это да.
А вы можете обосновать, что точность увеличивается именно в 2 раза, а не, скажем, в полтора? Откуда вообще взялась эта цифра? Ни в русской, ни в английской википедии её нет.
Конечно же не в 2 раза, а в 10^N для десятичных чисел или в 2^N для двоичных чисел. Где N — количество разрядов, выделенное для учета дробных значений, которые вышли за пределы разрядной сетки операционной мантиссы. Учет этих значений уменьшает абсолютную погрешность и как следствие относительную.
Выглядит ещё менее убедительно. Если честно, выглядит так, как будто вы эту формулу взяли просто из головы. Как минимум потому, что в ней отсутствует количество суммируемых элементов. Кроме того из неё следует, что если для хранения накапливаемой погрешности вместо double взять long double, это приведёт к экспоненциальному росту точности. Что сомнительно. Ведь вычисления производятся по-прежнему в оригинальном формате.
В примере из Википедии для старших цифр десятичных чисел отведено 6 разрядов и столько же для погрешности. В общей сложности получается, что число представляется 6+6+12 разрядами. Каждый новый разряд увеличивает точность представления числа в 10 раз для десятичных чисел. Вот и получается, что за счет дополнительного программного регистра точность повысилась в 10^6 раз.
А вы не используйте алгоритмы, которые допускают такое накопление.
Например, для обучения свёрточных нейронных сетей зачастую достаточно даже не float, а half-float (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)
Так в чем неточность? В термине «действительное число»? Да, я с вами согласен, это число рациональное. Почему-то в литературе, в том числе в стандарте IEEE754, дробные рациональные числа называются действительными. Я не стал менять эту традицию, т.к. для данного случая это не принципиально.
Хорошо. Но теперь Вам стало понятно, что равенство (из Вашей статьи):
0.06543455=1.00001100000001010001101〖2〗^(-4)

лишь приближенное? Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.

Теперь заменяем везде в статье, где слева десятичная дробь, а справа двоичная, знак точного равенства на знак приближенного равенства. И видим теперь, что Ваш текст «разошелся» с математикой. Попробуйте его скорректировать чтобы и математика не пострадала, и идея вашей статьи. Я сомневаюсь, что это возможно.

Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления? А если бы в компьютере была использована троичная система счисления? Или пользователь бы троичную систему счисления использовал, а компьютер десятичную? Где потерялась симметрия?

Ну и наконец, во всех физических и математических расчетах рациональных чисел не бывает. Чему там равна скорость света? А никто точно не знает. Лишь доверительный интервал известен (между двумя рациональными числами). Плевать на ошибку оцифровки в значительном числе случаев.

Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.

Вы правы, в приведенном равенстве надо поставить знак приближения. Число справа, если его привести к десятичному виду не будет равно десятичному числу слева. Вот эта разница в представлении и дает ошибку в вычислениях.
И видим теперь, что Ваш текст «разошелся» с математикой.

Так где расхождение?
Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления?

Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.
Плевать на ошибку оцифровки в значительном числе случаев.
Полагаю, что этот акт надо оставить на усмотрение человека. А машина должна считать точно.
Так где расхождение?

Так вы же сами совершенно правильно ответили:

Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.

… только теперь надо бы заголовок статьи поменять?

Я не понял, по названной нами причине ошибки при вычислениях имеются или нет? Я же про ошибки говорю, которые обусловлены несоизмеримостью систем счисления. Так что неверно в названии?
Это не ошибки, а погрешности. Т.е. не bug, а feature. И Вы правильно сейчас написали, что причина в невозможности точно преобразовать числа конечной длины из N-ричной системы счисления в M-ричную. Т.е. пока мы используем лишь одну систему счисления, проблемы нет. Но Ваша статья называется «Фатальные ошибки двоичной арифметики...».
Вы напрасно поставили многоточие. Речь идет именно об ошибках (или погрешностях) при двоичных вычислениях с десятичными ЧПТ. Не буду спорить по поводу семантики понятий bug и feature, но на мой взгляд последствия очевидны, как в том, так и в другом случае.
Вы напрасно поставили многоточие.

Не напрасно. В заголовке Вашей статьи (Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой) нет упоминаний про десятичную систему счисления.

Речь идет именно об ошибках (или погрешностях) при двоичных вычислениях с десятичными ЧПТ.

Ну нет внутри современных компьютеров (ну уровне железа) десятичных чисел с плавающей запятой. Только двоичные.
Да, я с Вами согласен. Надо было уточнить про десятичные числа. Боюсь уже поздно.
анекдот в том, что как раз скорость света (в м/с ) является целым числом (точно 299792458), через которое определяется метр.
Ваша правда. Неудачный я пример привел.
Дело не только в формате представления чисел.

Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе»

Во вторых, на самом деле FPU считает «не совсем по стандарту». В тоже же библиотеке, например GCC, масса воркэраундов. AFAIK -ffast-math будет выполнять расчеты только инструкциями FPU, и результат может вас удивить.
Да, на выходе FPU происходит усечение двоичного представления числа. Но именно усечение двоичного числа и приводит к появлению хвоста, значение которого априори определить не представляется возможным. А за счет нормализации часть хвоста оказывается в области ожидаемых верных цифр.
Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе».
Да похоже я слегка загнул — внутренние вычисления происходят в x87 в 80-битном виде. Т.е операнды расширяются к 80-бит, выполняются вычисления и после этого происходит обратная конверсия с понижением точности.

Если же используются SSE/AVX то там максимум 64-бит числа с плавающей точкой.

Утверждается (например на stackoverflow), что с предварительным расширением до 80-бит и без него можно получить разные результаты вычислений.
Если мне мою карму не опустят ниже плинтуса, в следующем топике я постараюсь представить свое видение этого вопроса. А здесь еще раз повторюсь, расширение количества бит под двоичную мантиссу не помогает избежать ошибок вычисления. Это я попытался показать на двух форматах float и double.
x87 все вычисления производит в 80-битном расширенном формате, и для double и для float
И вот когда мы отбрасываем двоичный хвост, возвращаясь к double или float, тут и вырастает десятичный хвост, который все и портит.
В общем случае «хвост» в FPU уже есть, просто на выходе мы получаем меньшую точность этого хвоста. Математики, которым критичен этот хвост, и так в курсе того, что компьютер может хранить лишь конечное множество всех возможных значений, в курсе мантисс и экспонент, и в курсе различий между численным и аналитическим решением уравнения. Выводы делайте сами!
Ну, про точность хвоста, мы ничего не можем сказать, нам про него ничего не известно.
Математики, которым критичен этот хвост, и так в курсе того, что компьютер может хранить лишь конечное множество всех возможных значений, в курсе мантисс и экспонент, и в курсе различий между численным и аналитическим решением уравнения
.
Но, позвольте, а как пользователю относиться к тому, что мы получаем на выходе Excel? Математики может и в курсе, что некоторые числа могут получаться неточными. А как пользователю определить, когда можно доверять результату, а когда нет?
Позвольте, как мне прооперировать себя, не владея скальпелем? Срочно нужна статья о хирургии для обычных пользователей. Причём, написать её должен не хирург, а другой обычный пользователь, случайно отрезавший себе палец.
Ваш юмор я оценил. Но как быть с расчетами?
А как учитывать, если из заявленной точности — 15 достоверных цифр после запятой для нормализованных чисел формата double, верными оказываются только 7?
Озвучьте вашу версию ответа, пожалуйста.
Думаю, ответ такой. Не использовать в расчетах с действительными (рациональными) числами двоичную арифметику в том виде, как она реализована в стандарте. Причем не важно, как она реализована, на аппаратном или на программном уровне. Вся беда в том, что проблемы начинаются, когда десятичное число содержит дробную часть. Целые числа не приводят к ошибкам представления. Поэтому, если представлять числа в виде ЧПТ с целочисленной мантиссой, эти проблемы не возникают. Возникают другие проблемы, но они не системные и преодолимы. Попытка ответить на ваш вопрос была предпринята в моем топике https://habrahabr.ru/post/272251/. В комментариях можно найти ссылку на PDF-файл.
Теперь стало понятно, где у вас «болит» (ранее эту статью не читал). Думаю, вам стОит не евангелизированием своих идей заниматься, а реализовать вашу альтернативу в виде библиотеки и сравнить её на каких-нибудь бенчмарках (поиск ранга матриц размером триллиард на триллиард, рассчёт какой-нибудь несущей конструкции с триллиардом элементов и т.п. — и по скорости, и по точности, и по стабильности этой точности) с существующими библиотеками и решениями. Мне кажется, что среди этих альтернативных решений вы и для себя откроете множество интересных подходов к контролю точности вычислительных задач.
Простите, я не понял, а как же быть с ошибками вычислений при самых простых арифметических операциях? Или они не актуальны при поиске ранга матриц размером триллиард на триллиард?
Так же, как «были» до этого.
А Вы не используйте Excel для расчётов ядерного реактора и всё будет хорошо :)
Честно-честно.
В статье речь идет ни об ошибках в Excel, а проблемах работы с ЧПТ, которые представлены в двоичном коде. Excel был взят для иллюстрации.
Вот программка, которую по моей просьбе написал Дмитрий Пикулик.
/*
* 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

Сравните с тем, что получится на калькуляторе.
Задайте себе вопрос, почему, при математических расчётах ЧПТ на ПК сравнение выполняют не так:
double a = f1(), b = f2();
if ( a != b ) {… }
а так:
double a = f1(), b = f2();
if ( fabs( a — b ) > EPSILON ) {… }
и на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?
Всё известно, кто хочет точности — пользуется специальными библиотеками.
Разница между двумя числами double a = 0.66 и double b = 0.659999996423721 составляет 0,000000003576279. Вопрос, какое должно быть EPSILON, чтобы убедиться, что это число равно числу 0.00000000357627905067659

на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?


Ну, статья не про IEEE754, а об ошибках, которые получаются в результате арифметических операций при использовании двоичной арифметики для десятичных ЧПТ. Эти ошибки не совсем очевидны, так как в большом количестве случаев результаты вычислений будут верными при использовании стандарта. Но где гарантия, что мы не столкнемся именно с теми немалочисленными ситуациями, когда будут получены совершенно неприемлемые результаты.
Вопрос, какое должно быть 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,00001
По-моему кавычки в словах «всего лишь» надо убрать, т.к. 24 двоичных разряда мантиссы формата float обеспечивают представление десятичного числа с точностью до 7 знаков после запятой.
Кавычки тут больше для привлечения внимания, допустимая точность для float не 7, а пять знаков после запятой, именно это и отражает постоянная FLT_EPSILON
Если мы говорим о представлении десятичного числа в двоичном коде, то 24 двоичных разряда мантиссы нормализованного числа позволяют представить любое десятичное число с 7 верными цифрами. Почему же вы ограничились 5 знаками?
Если грубо — 5 знаков это допустимая точность вычислений для float, 9 — double. Любые вычисления, требующие большей точности требуют смены типа переменных. Например, если мы считаем некоторую величину где будут учитываться 5 и 6 знаки после запятой — float уже не подходит, его точности не хватает, выбираем double. Если нужна точность более 9 знаков — дабл так же не подходит. Это основы программирования с ЧПТ, учат ещё когда проходят эти числа.
Вы сейчас боретесь или с ветряными мельницами.
Посчитайте в формате float, чему это равно: 1234,567-1234,56=?
У меня получилось 0.0069580
#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 все знают.
Да это уже на троллинг со стороны топикстартера похоже, если честно. Когда вы записали 1234.567 во float, вы уже ошиблись в 5 десятичном знаке после запятой. То же самое с 1234.56.
Так и я о том же. Как только вы преобразовали десятичное число к двоичному, вы ошиблись не в 5 знаке, а в 10000… знаке. Так как получили бесконечную дробь.
В пятом десятичном знаке.
В самом большом числе из приведенных всего 3 знака после запятой (1234.567). А вот после конвертации оно становится
1234.56710 = 1.0011010010.100100010010011011101001〖∙2〗^10
=1234.5670166015625
Так про какой 5 знак идет речь?
Когда вы записали 1234.567 во float, вы уже ошиблись в 5 десятичном знаке после запятой.
Остальные я не считал: 7->(4->8->6->2->4). И это неважно. При ошибке в пятом десятичном знаке после запятой мы имеем 8 правильных десятичных знаков в представлении исходного числа. Большего мы от float и не ждем.
Ошибся не я, а природа, которая все это устроила. Еще раз повторюсь, речь идет не о точности конвертации чисел из одной системы счисления в другую, а о результатах вычислений.
1) И какова относительная погрешность?
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.
3) Какова относительная погрешность для чисел, которые вы в реальности записали в память?
1) И какова относительная погрешность?

Если говорить о погрешности представления — разная для разных чисел
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.

Я все время именно о таких числах и веду речь. Представимые числа — точные и с ними проблем нет до тех пор пока в результате арифметических действий они не превращаются в приблизительные.
В данном конкретном случае какова относительная погрешность вычислений?
Не совсем так. Скорее, так:
|a-b| < epsilon*max(|a|, |b|)
Спасибо, не знал этого способа.
Смыл в том что расстояние между соседними машинными числами в формате IEEE754 зависит от их абсолютной величины. Например, следующее за 10^8 во float будет 10^8+8. Поэтому абсолютная точность, скажем, 0.1 при таких порядках будет в принципе недостижима.
Я не совсем понял. У нас рассматриваются все числа, у которых один десятичный порядок.
я уточнил, зачем нужно проверять на равенство считая относительную погрешность, а не абсолютную
Я про проверку на равенство советов не давал
Так исходный мой комментарий и не вам был адресован.
Извините, закопался в коментах.
в FPU есть флаг точности с возможными режимами:

00 – 24 бита мантисса, 7 бит порядок;
10 – 53 бита мантисса, 10 бит порядок (двойная точность);
11 – 64 бита мантисса, 15 бит порядок (расширенная точность).
И что из этого следует?
Это было уточнение комментария «x87 все вычисления производит в 80-битном расширенном формате, и для double и для float».

Из этого следует, что при выполнении вычислений на FPU вы будете получать разные ответы в зависимости от того, в каком режиме точности он работает. Более того, вы будете получать различные результаты и в зависимости от того, как именно компилятор оптимизирует ваш код и в каком конкретно формате он хранит промежуточные вычисления.

Стек FPU ограничен восемью регистрами. Пока остаются свободные регистры для хранения промежуточных данных, очевидно, их точность остаётся неизменной. Но как только свободные регистры заканчиваются, компилятор вынужден сохранять их во внешней памяти — и обычно это делается в 64-х битном формате, с потерей 80-битной точности (как показало изучение ассемблерного кода от компиляторов Intel и Microsoft).
Эти настройки точности больше для того, чтобы результаты не были точнее чем вы сами того ожидали, проверяя на вычислениях ограниченной точности (взято отсюда 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


Когда вы работаете на сопроцессоре, вы накапливаете результат в одном регистре расширенной точности, другие вам для этого не нужны.
а как тогда считает калькулятор? или он не двоичный?
Для точных вычислений существует двоично-десятичный формат, который реализует десятичную арифметику.
Машинные операции с вещественными числами имеют ограниченную точность, что Вы и продемонстрировали на примерах.

В эксплуатационной документации к первым ЭВМ так и писали, что машина получает не сам результат математической операции, а доступное приближение к нему. Потом как-то все привыкли, и писать перестали.
Используя тот или иной инструмент, я должен знать, какую точность гарантирует этот инструмент. Если для double гарантирована точность до 15 десятичных знаков после запятой для нормализованных чисел, а по факту я имею всего 7, то как можно использовать этот инструмент?
а где написано именно про гарантированную точность?
В каком примере вы имеете 7?
0,789012345005176
А мы ожидали получить число: 0, 789012345
Погрешность меньше допустимой 1Е-9, так что всё укладывается в рамки допустимого.
Там нет примера, только Ваши утверждения.

#include <stdio.h>
int main () {
double d;
d = 105.3256-105.32;
printf («d = %lf\n», d);
return 0;
}

d = 0.005600

Если Эксель вам врёт, то причём тут машинная арифметика? Он вообще с символьными данными работает.
Посмотрите в комментариях чуть выше. Там приведен пример для формата double.
Вы можете конкретно написать пример? Из Ваших реплик ничего не понятно.
Не могу, хабр. ограничивает ресурсы. Пишите в личку.
Если Вы имеете в виду свой комментарий “Вот программка, которую по моей просьбе написал Дмитрий Пикулик”, то в этой программе:

1) отсутствует определение переменной x, поэтому она не может быть скомпилирована;

2) после вставки определения “double x;”, результат, выдаваемый программой, соответствует теоретическому с точностью до 16 десятичных цифр, что отвечает точности формата double.
Посчитайте разность: 123456,789012345-123456.
0.789012345005176, разница в 18-м десятичном знаке.
123456,789012345-123456=0,789012345. Согласно вычислениям на калькуляторе виндоуз.
Ну берите какой-нибудь язык PL/I и тип decimal fixed, если вас интересует точное представление десятичных чисел. Как верно указано в соседней ветке, настоящие физические параметры не имеют точного представления ни в одной системе счисления, поэтому совершенно неважно, как их записывать. Главное, чтобы погрешность алгоритма соответствовала требованиям решаемой задачи.
Вопрос в цене-времени-сложности. Нет таких калькуляторов, которые работали бы на указанных вами принципах.
Каких именно принципах? Практика программирования показала, что точное представление десятичных чисел никому не нужно, кроме бухгалтеров, которым достаточно целых копеек. Поэтому от десятичных дробей в современных широко распространённых системах программирования отказались. Остальные вещественные числа – это приближения иррациональных значений (так как мощность множества иррациональных чисел бесконечно больше мощности множества рациональных, то вероятность точно попасть в рациональное значение равна нулю).
Причём, так как изначально практически все источники вещественных цифровых значений в современном мире имеют двоичную природу, то представление чисел в десятичной системе их только портит.

Рано или поздно человечество перейдёт к шестнадцатиричной в быту.
a = 123456.789012345-123456.0;
printf («a = %.15lf\n», a);

a = 0.789012345005176

Кто вам сказал, что Эксель работает с числами в формате double и в соответствии с IEEE 754?
К нему и предъявляйте претензии, а не к формату double. То, что в Экселе много залепух в арифметике, давно известно.
Microsoft Excel was designed around the IEEE 754 specification to determine how it stores and calculates floating-point numbers.

Эксель в статье был использован как инструмент для иллюстрации правильности приведенных вычислений в стандарте IEEE754.
Запрограммируйте приведенный вами пример на любом языке, в котором используется формат IEEE754, задав формат дабл, и вы получите тот же результат. Так что эксель здесь не причем.
Тут не написано, что операции с числами в Экселе соответствуют стандарту IEEE 754. Написано, что Эксель спроектирован где-то вокруг этого стандарта (даже не знаю, как корректно перевести это бессмысленное маркетинговое утверждение).

Я свои примеры даю на языке Си с использованием арифметики IEEE 754.
Курс дзена молодого бойца: попробуйте перемножить, а тем более уж поделить Точно одно-двух байтные целые числа на AVR.
Мастер! Попробуйте с точностью до 6 знака после запятой найти частное 2/3 в двух разрядном калькуляторе.
Я никого не хотел обижать. Просто AVR может ошибаться уже в 4-х значных числах, и без запятой.
Простите, но при чем тут AVR? Да, там нет сопроцессора, поэтому все операции с плавающей запятой производятся программно, это может сделать сам компилятор или использовать библиотечные реализации (http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_math).
Однако, вы можете реализовать все операции самостоятельно, с любой точностью, какой пожелаете, ограничиваясь объемом памяти кристалла, конечно же.
На самом деле да, все как Вы сказали, я имел в виду скорее компилятор. Хотя не могу сказать по всем, но avr-gcc (самый популярный) и icc (очень редкий) генерируют неточный код. И у меня даже есть маленькая функция для быстрого и более точного деления на 10 (каждый раз при отображении чисел нужна).

Но и это не все, там есть аппаратная функция FMUL которая умножает два байта. И она может ошибаться на 1-2 значения.

P.S. еще раз: я про целые числа, не про числа с плавающей запятой. Если даже в операциях с целыми числами могут быть такие ошибки вычислений, то что уж говорить про числа с плавающей запятой. И стоит добавить что у чисел с плавающей запятой неточность ожидаемая, то неточность при использовании целых чисел может застать в расплох. И да, я про переполнения конечно же.
Как можно ошибиться при умножении? Вы о чем вообще? Команда FMUL предназначена для операций с фиксированной запятой она сдвигает результат влево чтобы запятая результата оказалась на том же месте, что и у входных аргументов.
Это значит, что если попытаться с её помощью перемножить два целых числа вы получите число в два раза большее чем ожидали.
Ныряю в пепел, стыд и позор. Про FMUL я просто слышал и не правильно понял, признаю (я С-шник). Но и в остальном я не могу воспроизвести ничего. Даже ошибку в делении. Заминусуйте начальные комментарии чтобы никто ересь не читал. С чего я это все взял — не знаю, может раньше так было, а может у меня повреждение памяти, жаль с ECC нельзя поставить.
Для получения точного результата, при умножении целых беззнаковых чисел, необходимо, чтобы результирующий регистр имел число разрядов не менее суммы разрядов сомножителей.
Я не про переполнения конечно, иначе этот диалог, начатый мной, не имел бы смысла. Сходу не найду, но вот например есть калькулятор fmul calc — позволяющий смотреть где ошибается микроконтроллер avr при использовании этой инструкции (аппаратная инструция целочисленного умножения, принимает два байта, результат записывается в двухбайтовую переменную). Не знаю на сколько точен калькулятор, но эти ошибки имеют место быть.
Что автор-то хочет, мне — не понятно…
По моему, у него, каша в голове: Excel, какие-то листинги программы, обещания 7 знаков после запятой наследственной грамотой…
Вы сами-то понимаете о чём речь?
Если вы показываете листинг программы, где написанна десятичная константа, то она не десятичная, это всего лишь соглашение, точно так же как кодировка листинга (может быть в utf8, но это не значит, что print(«hello world») небудет в ANSI) и вывода программы. Если в листинге написано a := 0.6, почему вырешили, что в компьютере материализуется довоичнодесятичный аппаратный блок арифметики, а не разумное: «пишите как удобно, компилятор сам преобразует»?
Значит вычёркивайте вообще все слова «десятичные» из текста.
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков? Теория говорит, что будет уже 4 точных знака и не надо делать выпученные глаза и тыкать нас в это носом, идите и читайте книжки.
Уважаемый! Нельзя ночью писать коменты, Судя по всему, при изложении своих мыслей, вы пользуетесь принципом «пишите как удобно, компилятор сам преобразует».
Если по существу,
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков?

Если следовать вашим знаниям, то в формате float, после 7 шагов суммирования, мы уже имеем совершенно неверный результат, даже, если складывались точные числа. Ну или 16 шагов для дабл.
Когда говорят о точости, делают оценку сверху. Так что ваш ответ зависит от того, как считать, вы же не будете спорить с тем, что мы можем только приближённо прогнозировать худший вариант, а что будет на самом деле надо уже смотреть.
Если в кратце, то замените разряд на X и подумайте сами, способен ли он после сложения повлиять на старшие знаки?
1.0X+1.1X= это может быть, например, так 1.09+1.19 =2.28 а не 2.1X, точность — упала. После 7 шагов она упадёт ещё больше, но оценка сверху по частям и общая оценка сверху будут разниться, так же как и в бухгалтерии сумма НДС товаров не равна НДС от суммы товаров.
Проще говоря, если вам числа даны с определённой точностью, суммирование будет накапливать погршеность, умножение её преумножать, на каждой итерации всё больше разрядов могут отличаться и на целую часть это тоже распространяется. Есть попытки бороться с этим в финансах внося стохастическую составляющую. но по факту смысл не меняется и совсем от этого никуда не деться, можно лишь немного изменять масштабы бедствия в конкретных практических случаях.
Я бы в подобный случаях советовал использовать hexfloat (%a) формат, чтобы различить, где у вас исходная десятичная константа уже с округлением.

  cout << 0x0.123p-1 << endl;
  cout << std::hexfloat << 6.6;

Может я не прав, но тем кто про эти очевидные вещи знает — статья не очень интересна.
А тем, для кого это "открытие" лучше было бы еще привести практические рекомендации (и примеры придумать/привести) типа :


  1. Никогда не сравнивай результат вычисления double/float типа с константой на равенство.
  2. Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым.
    3… и т.д.

впрочем, хозяин барин...

Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым
.
Это, если использовать медицинские аналогии, которые здесь приводил один пользователь, все равно, что сказать: " Я вам даю таблетку от головной боли, но последствия непредсказуемы".

Вообще то с таблеткой обычно дают рекомендации по использованию. Если уж пошли такие удаленные аналогии.


А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.


Причем эта тема весьма стара и классическая. Начиная с FORTRAN 66, когда программированием начали заниматься люди, не вникающие (да не надо им это) как выполняется, например, работа с числами с плавающей точкой на разных архитектурах и пр.
Да самое элементарное (одно из..) — выравнивание (умножение + деление результата на константу) там где нужно исходя из задачи и предполагаемой размерности чисел.


Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?

А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.

Ну да, как у Макаренко в «Республике ШКИД» — «Теоретически она лошадь, а практически она падает» Вы действительно считаете, что все проблемы с обработкой ЧПТ решены? А зачем INTEL в железе реализует обработку ЧПТ в двоично-десятичном формате?
Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?

Чуть ваше я как раз объяснял вкратце с каким форматом работает калькулятор. Так что, кто не знал, видимо, уже знает.
Вы действительно считаете, что все проблемы с обработкой ЧПТ решены?

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


И все…
Все остальное — это Вы уже сами за меня додумывает и высказываете.

А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.

Вы считаете, что этой куче чего то не хватает? и поэтому
привести примеры типичных классических ошибок программиста при работе данными в формате с «плавающей точкой» и как их избежать

Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.
Извините, но просто поговорить, действительно не интересно.
Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики.

Я вначале подумал, что это такая тонкая шутка, как вступление к лекции "для чайников". Но Ваш последний комментарий показал, что это не было шуткой, а сказано на полном серьезе:


Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.

Вы действительно считаете, что изложили в статье что то очень оригинальное и никому не известное?
Тогда "Извините, но просто поговорить, действительно не интересно.".


Ну конспекты лекций я сканировать и отправлять не буду (если найду их еще).
А так google: "проблемы с плавающей запятой учебник".

К сожалению формат дискуссии не позволяет сделать подробный анализ выкладок в указанной вами статье. Но, если вы с ней знакомы, то, во-первых, в ней рассматривается частный случай:
«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,00000012
Чисто формально, абсолютная погрешность разности равна сумме абсолютных погрешностей уменьшаемого и вычитаемого, и это никак не связано со способом вычислений и представления чисел. Относительная погрешность может получиться какая угодно, в зависимости от соотношения порядков результата и операндов.
У нас два точных числа a=105.3256 и b=105.32. Какова их абсолютная и относительная погрешность? Она равна нулю.Преобразуем их в двоичные числа и получаем приблизительный эквивалент точных значений. Чем меньше длинна двоичной мантиссы, тем менее точным становится десятичный эквивалент двоичного числа. При арифметических действиях над приблизительными числами всегда получаются приблизительные результаты. Абсолютная разность между полученным точным результатом и полученным приблизительным результатом дает абсолютную ошибку. Отношение абсолютной ошибки к точному числу дает относительную ошибку.
Относительная погрешность может получиться какая угодно, в зависимости от соотношения порядков результата и операндов

О каких соотношениях порядков результата и операндов идет речь? Желательно с примерами.
Если уж мы говорим о погрешностях, давайте изъясняться метрологически корректно. Может быть, в идеальном платоновском мире и существуют точные числа, но в реальности каждое значение имеет погрешность, связанную с различными причинами. В частности, всякое число, записанное в позиционной системе счисления, имеет погрешность представления, равную половине цены младшего разряда. Для числа 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.

Заметьте, это всё чисто аналитические выкладки, не имеющие пока никакого отношения к компьютеру и его представлению вещественных чисел.
Может быть, в идеальном платоновском мире и существуют точные числа, но в реальности каждое значение имеет погрешность, связанную с различными причинами

Берем 5 яблок и отнимаем от них 2. Получаем 3? Или все же 3+- немножко? Какой ответ верен? Яблоки могут быть совершенно разные по размерам. Если мы считаем в столбик разность двух чисел 105.3256 и 105.32 мы получаем в ответе 0.0006. Или +- что то еще? Калькулятор не знает предыстории чисел с которыми имеет дело. Это вы должны интерпретировать полученные результаты.
3 плюс-минус немножко, конечно. Пока вы отнимаете яблоки, они истираются, или, наоборот, что-нибудь с рук на них налипает. Проще заметить, вычитая 2 тонны яблок из 5 тонн.

(Говоря о единичных яблоках, это, в практических целях, 5.0 и 2.0. Половину яблока ещё различаем при подсчёте количества, а 0.05 – уже нет.).

То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно. Инженеров, как правило, отучают вычитать 105.32 из 105.3256 ещё на первом курсе.
Пока вы отнимаете яблоки, они истираются, или, наоборот, что-нибудь с рук на них налипает.

Если какое-нибудь яблоко не прилипнет к рукам раздающего, они будут разложены ровно на две кучки по 5 и 3 в каждой.
То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно

Ну и слава Богу. Не его это калькуляторное дело интерпретировать результаты вычислений.
Вы почему-то хотите узурпировать всю вычислительную технику под метрологические задачи. Но ведь есть еще всевозможные задачи: математического моделирования, криптография. И, в конце концов, не лишайте человечество узнать значение числа пи в N-том знаке после запятой:).
Я не возражаю, но тогда вообще не надо говорить про погрешности.
Я бы про них и не говорил, если бы они не возникали в процессе вычислений дробных десятичных чисел инстументарием двоичной арифметики.
Так вы сначала в аналитической теории разберитесь, что и как вы хотите посчитать. А если вы считаете конечную запись десятичного числа за абсолютно точное значение, то дальше уже из этой ложной предпосылки можно сделать какие угодно выводы.
У меня 3.00 детей в десятичном исчислении. Это истина. Это конечная запись, больше детей у меня не будет. Какие ложные предпосылки вы в этом усматриваете и какие выводы вы можете сделать? Вариант — дети не от меня, исключается:)
А если серьезно, то точные числа существуют, а натуральные числа вообще связаны исключительно со счетными задачами. Это потом появились рациональные числа, в результате операций сравнения. Что больше (меньше) и во сколько раз. И даже для них, если числа соизмеримы, т.е. в них укладывается ровное количество меры, они будут точными. Не в смысле точности измерения, а в смысле количества мер, которые в них укладывается. Пример: эталон — человек обыкновенный. Сколько человек в кинотеатре? Скажем — 201. Это точное число или приблизительное?
Вообще, понятий точности много. Большой энциклопедический политехнический словарь понятие точности определяет как «степень приближения истинного значения рассматриваемого процесса, вещества, предмета к его теоретическому номинальному значению» [ dic.academic.ru/dic.nsf/polytechnic/9524/ТОЧНОСТЬ ]. Для численных задач, мне кажется, это верное определение. Поэтому результат, полученный в вычислительном устройстве, тем точнее, чем он ближе к вычисленному столбиком вручную.
Извините, если сделаете свой комент, скорее всего отвечу послезавтра. Уезжаю.
Когда вы говорите о своих детях, то это их нумерация натуральными числами в вашем списке детей, а не измерение количества, поэтому в данном случае говорить о погрешности не приходится.

Что касается точности, то в инженерном деле стараются не использовать этот термин, если стремятся к строгости терминологии. Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.
Когда вы говорите о своих детях, то это их нумерация натуральными числами в вашем списке детей, а не измерение количества, поэтому в данном случае говорить о погрешности не приходится.

Считаю, все ли дети дома? Один, два, три. Так, общее количество — приблизительно 3!
Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.

Не качественно, а количественно. В формате флоат можно представить точно до 7 значащих десятичных цифр. В формате дабл — до 16 десятичных цифр. И хотя, после перевода в десятичное представление дробного двоичного числа конечной длительности, вы можете получить бесконечную дробь, первые 16 значащих цифр десятичного числа, проконвертированного из 2 в 10 систему, будут точными.
Считаю, все ли дети дома? Один, два, три. Так, общее количество — приблизительно 3!


Если вы измеряете наполненность дома своими детьми, то ребёнок, высунувшись в окно, может быть дома на любую дробную часть.

Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.

Не качественно, а количественно. В формате флоат можно представить точно до 7 значащих десятичных цифр.


Нет. Никакое количество десятичных цифр в дробной части нельзя представить в двоичном виде точно, о чём Вы сами же и пишете. Можно говорить о том, что погрешность представления чисел в компьютерной арифметике одинарной точности не превышает их погрешности при записи в десятичной системе с 6 значащими цифрами.

Я же выше говорил только о том, что “одинарная точность” и “двойная точность” – это некое условное обозначение форматов представления чисел, а не то, что есть некая формальная метрика “точность”, различающаяся ровно в два раза.

Если вы измеряете наполненность дома своими детьми, то ребёнок, высунувшись в окно, может быть дома на любую дробную часть.

В качестве наказания я их всех запер в кладовке, предварительно законопатив все щели. Пересчитал, получилось приблизительно 3 штуки.
Нет. Никакое количество десятичных цифр в дробной части нельзя представить в двоичном виде точно, о чём Вы сами же и пишете.

Я писал: «И хотя, после перевода в десятичное представление дробного двоичного числа конечной длительности, вы можете получить бесконечную дробь».
0.5(10)= 0.1(2), 0.625(10)=0.101(10) и т.д. Это точные равенства.
Математика создавалась не для решения инженерных задач. Это для решения инженерных задач привлекается математика.
Детей заперли в кладовке, а состриженные ногти остались снаружи. Чешуйки кожи постоянно отслаиваются. Всё имеет определённую точность рассмотрения.
Извините, думал это будет понятно без уточнения. В скобках указаны системы счисления, а не периодичность дробей.
Чешуйки кожи постоянно отслаиваются. Всё имеет определённую точность рассмотрения

Я все таки не пойму, какое количество детей заперто в кладовке? За единицу измерения берем голову или килограммы?.. Или мы не имеем права определять количество детей по головам? Тогда, если определяем только по массе, то, простите, из туалета только что вышло 45,46 кг. Васи:), а заходило 45,8. Посчитал по головам, получилось — ровно одна. Сколько Вась мы имеем в наличии? По вашему, где-то 45(+-1) кг. А он растет и становится его уже 92 кг. Было в кГ. 45,46 Васи, а стало в два раза больше — 92кг. или 92(+-)кг. Сколько взрослых Вась мы имеем?
Про скобки сначала невнимательно прочитал. Редкая нотация, поэтому без вычислений контекст не понятен. Потом понял и исправил.

Что касается детей в кладовке, то всё правильно, сначала надо определиться с тем, что и как мы измеряем. А когда вы под конец в точности определения дойдёте до квантовых величин, то придётся учесть, что предметы, кажущиеся нам материальными объектами, на самом деле – просто локализации плотности распределения вероятностей, и нахождение Васи в кладовке хотя и очень близко к единице, но с очень небольшой вероятностью он находится в любом другом месте во Вселенной.
Извините, изотерикой не занимаюсь, поэтому ничего возразить не могу.
Это физика вообще-то.
предметы, кажущиеся нам материальными объектами, на самом деле – просто локализации плотности распределения вероятностей

Такой физикой я тоже не занимаюсь.
Что касается дробей, то некоторые числа могут быть представлены точно, но в общем случае произвольные десятичные значения — нет.
Мне нечего на это возразить. Хотелось бы только расширить это утверждение и на целые числа. Только с оговоркой, что не некоторые, а все целые десятичные числа могут быть точно представлены в двоичном коде, а все дробные двоичные и все двоичные целые числа могут быть точно представлены в десятичном виде.
Не путайте тёплое с мягким.
5 — число целое, хранится в целочисленном типе.
Хотите точные операции с дробями — используйте fixed point арифметику.
Хотите точные операции с дробями — используйте fixed point арифметику.

Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой? Зачем тогда ЧПТ?
Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой?
Зачем тогда ЧПТ?

Fixed point имеет фиксированную абсолютную точность, тогда как Floating point — фиксированную относительную точность.

64-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.
64-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.

Во-первых, 64-битное целое двоичное число может обеспечить представление десятичного числа с точностью не выше 16 значащих цифр. А во-вторых, в целом числе отсутствует дробная часть, которая и приводит к описанным в статье неприятностям.
Хотите точные операции с дробями — используйте fixed point арифметику.

Для точных рациональных вычислений нужно использовать специальные средства, например, класс «ratio» в c++, который хранит отдельно числитель и знаменатель в целочисленном виде.
Инструкции BCD устарели и с переходом на x64 убраны.

До сих пор помню, как мне гад препод, из вредности, срезал 5 (на 4) вопросом про разрядность регистра 80487 (FPU сопроцессор, если кто не помнит). На лекции его не ходил. Хотя каждый день на кафедре встречались.
Одна 4-ка за 4 года.


Базовые знание по работе комп. потрохов очень помогают. Меньше ситуаций "Это магия какая то… почему результат не верный".

Я наконец-то понял, что хотел сказать автор.
Сначала несколько пунктов:
1. Никакие FP не имеют к сути статьи никакого отношения.
2. Количество значащих разрядов и точность чисел тут тоже не при чём.
Заголовок статьи должен был быть таким: «Фатальные ошибки двоичного представления дробных десятичных чисел.»

Суть такова: мы бухгалтеры и считаем денюшку, у нас есть десятичне числа с копейками, к примеру.
Есть факт, что конечные десятичные дроби при переводе в двоичные дроби, могут порождать бесконечные. В переводе на русский язык некоторые копейки в компьютере не могут быть представленными точно и будут записаны округлённой двоичной дробью. Округление, это зафиксированная погрешность, она после множества вычислений будет аккумулироваться и нарастать.
В итоге, результат вычислений на бумжке в столбик и с помощью программы на компьютере может не совпадать в копейках. что для бухгалтера очень неудобно.
Решение элементарно: двоично-десятичное представление полностью устраняет проблему.
Если я не угадал, то — сделайте мне лоботомию, я больше не хочу быть телепатом.
Головная боль бухгалтера — как разделить миллион рублей поровну на трёх человек.
Здесь даже точные десятичные вычисления не помогут.
Пошёл делать лоботомию :-)))
Я конечно далек от бухгалтерии, но может такое наивное решение пригодится. Создать графу «нераспределенные остатки» в которой учитывать копейки или доли копеек. Эти деньги временно хранить в банке. Когда следующий раз будете распределять миллион прибавьте к распределяемой сумме нераспределенные ранее остатки глядишь сумма и разделится. Если нет — опять остатки в банк.
Раз об этом зашла речь, а к чему вы, собственно, близки? Если вы далеки от бухгалтерии, математики и программирования, но при этом испытываете потребность решать фундаментальные ограничения чисел с плавающей точкой.
Ответ на этот вопрос как то прольет свет, на рассматриваемую проблему?
Я так понимаю, к контексту у вас вопросов нет? Есть вопросы к социальному статусу автора? В зависимости от ответа на последний вопрос, вы согласитесь с контекстом или нет?
Кстати, а что по поводу What Every Computer Scientist Should Know About Floating-Point Arithmetic? Я что-то не правильно посчитал?
Нет вопросов к социальному статусу автора.
Нет фатальной ошибки в том, что формат с ограниченной точностью имеет ограниченную точность.
Если вы хотите убедить, что в реализации IEEE754 есть ошибки — то надо не сюда писать, а в комитет по стандартизации.
Если вы изобрели новую, улучшенную технологию обработки чисел с плавающей точкой — то её надо срочно патентовать, пока этого не сделал кто-то другой.
Если вы боитесь, что «такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой», то в любом случае уже поздно что-то менять — в современных процессорах стандарт реализован аппаратно.
Нет фатальной ошибки в том, что формат с ограниченной точностью имеет ограниченную точность.

Ограниченная точность и точность без точных ограничений, извините за тафтологию, разные вещи.
Если вы изобрели новую, улучшенную технологию обработки чисел с плавающей точкой — то её надо срочно патентовать, пока этого не сделал кто-то другой.

Вы правы. Изобрел. И начал патентовать, пока только базовые операции.См. опубликованные евразийские заявки на изобретение № 201500284 и 201500168.
Если вы боитесь, что «такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой», то в любом случае уже поздно что-то менять — в современных процессорах стандарт реализован аппаратно.

Для спецтехники, по крайней мере при Союзе, разрабатывались оригинальные спецпроцессоры, а не брались универсальные ихние. Думаю, нужда в спецвычислителях и сегодня не отпала.
Я придумал способ точной обработки десятичных чисел с помощью двоичной арифметики. Тем самым мы получаем такой же точный результат, как при использовании двоично-десятичной арифметики, но по скорости почти не уступающей двоичной арифметике.
В ваших статьях я не нашёл строгого математического доказательства этому.
Я ничего и не обосновывал. Я привел несколько проблемных моментов без всякой аналитики, с целью убедиться в правильности своих представлений. На данный момент никто не смог опровергнуть те факты, которые приведены в статье. Отсюда следует, что проблема существует и решение ее — задача актуальная.
В следующем топике я планировал дать аналитические выкладки по данной проблематике.
Само же раскрытие технологии возможно только после того, как будет решен вопрос с приоритетом. Отечественное патентование практически ничего, кроме морального удовлетворения, автору не дает. Причем за это приходится не слабо платить. Для зарубежного требуются неподъемные средства. Вопрос пока завис.
Отсюда следует, что проблема существует и решение ее — задача актуальная.

Нет, это утверждение из приведенных тезисов не следует.
И потом, какая проблема-то? Все, что мы тут уже битую неделю обмусоливаем, для инженерных расчетов не является проблемой. Там практически нет точных чисел ни в десятичной системе счисления, ни в двоичной. Важны некоторые гарантии для относительной погрешности при вычислениях, по ним можно строить алгоритмы, устойчивые к ошибкам, как в исходных данных, так и в промежуточных вычислениях.
Ваши арифметические экзерцисы представляют определенный интерес для бухгалтерских расчетов. Но когда точно считают копейки плавающей точкой(запятой) пользуются редко.
Инженерам не интересно точное двоичное представление числа 0.2, если при этом они теряют в производительности. Более того, числа 0.2 у них на самом деле нет. Чаще есть какое-то близкое иррациональное (да пусть хоть рациональное), и им плевать, что компьютер ошибется при переводе его в двоичную дробь, потому что при записи исходной десятичной дроби они уже ошиблись. Попробуйте проанализировать, откуда в инженерных/научных расчетах берутся вещественные дробные числа, записанные в десятичной системе счисления?
Я придумал способ точной обработки десятичных чисел с помощью двоичной арифметики. Тем самым мы получаем такой же точный результат, как при использовании двоично-десятичной арифметики, но по скорости почти не уступающей двоичной арифметике.

Эта фраза означает, что способ будет интересен лишь производителям калькуляторов. Буду рад ошибиться.
И потом, какая проблема-то? Все, что мы тут уже битую неделю обмусоливаем, для инженерных расчетов не является проблемой.

Во-первых, кто вам сказал, что компьютеры созданы исключительно для решения инженерных задач? А во-вторых, если нет проблемы, то не о чем и говорить. Если же такое количество мнений, в том числе и вами, высказано на обсуждаемую тему, то вопрос, по крайней мере, не безразличен.
Там практически нет точных чисел ни в десятичной системе счисления, ни в двоичной.

Этот вопрос обсуждался выше по ветке с пользователем vadimr. Если есть что добавить, милости просим.

Важны некоторые гарантии для относительной погрешности при вычислениях, по ним можно строить алгоритмы, устойчивые к ошибкам, как в исходных данных, так и в промежуточных вычислениях.

Чем точнее представлено число, тем меньше его относительная и абсолютная погрешность, т.е. выше гарантии его верного представления. Или не так? Так точность актуальна для инженеров или нет?

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

Редко, но так хотелось бы чаще, ведь так удобно. Но вот точность вычислений не позволяет.
Инженерам не интересно точное двоичное представление числа 0.2, если при этом они теряют в производительности.

А этот INTEL в угоду бухгалтерам и ученым все увеличивает и увеличивает длину машинной мантиссы в ущерб производительности. А сейчас, я слышал, и вообще учудил, в железе реализовал арифметику ЧПТ в двоично-десятичном коде для точности. И еще. Народ совсем обнаглел, придумал сверхдлинную точную арифметику, которая производительность свела почти на нет. Ну совсем не думают об инженерах.

Попробуйте проанализировать, откуда в инженерных/научных расчетах берутся вещественные дробные числа, записанные в десятичной системе счисления?

Сейчас попробую. Ну, во-первых, когда конвертируют двоичную дробь в десятичную, если расчеты ведутся на компьютере. Во-вторых, когда находят отношение двух чисел (не зависимо от системы счисления), связанных какой-либо функциональной зависимостью. В-третьих… Больше не знаю.

Вам налоговая за эту копейку нервов на миллион выест.
Насколько я понимаю, в бухгалтерии имеются однозначные жесткие правила округления до копеек. Типа, как посчитать 13% от 123 руб 10 копеек.
В ваших рассуждениях имеются следующие ошибки:

1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.
2. умножение 0.06543455 на 139 — аналогично, при умножении необходимо иметь удвоенное количество разрядов для хранения результата.
3. деление 131 на 0.066 аналогично предыдущему. 1/0.066=15.151515...=15.(15) имеет бесконечное количество значащих цифр.
4. вычитание 105.3256 и 105.32 — здесь точности уже достаточно, и для получения корректного результата достаточно округлить результат до 4 цифр после запятой. Округление необходимо для отбрасывания погрешности, внесённой при преобразовании из 10-ого представления числа в двоичное. Продемонстрировать это можно, например, так:

float round(float v, float digits)
{
	float scale=pow(10,digits);
	return floor((v*scale+0.5f))/scale;
}

int _tmain(int argc, _TCHAR* argv[])
{
	cout.precision(7);
	cout << round(105.3256f-105.32f, 4.f) << endl;
	return 0;
}

Вообще, для проверки ваших математических выкладок я бы рекомендовал Вам использовать не Excel или калькулятор, а системы компьютерной алгебры. Например, в Wolfram Mathematica можно явно задавать точность чисел с плавающей точкой для обеспечения корректного результата:

In[1]:= 105.3256`7 - 105.32`7
Out[1]= 0.0056

In[2]:= 131/0.066`7
Out[2]= 1984.848
1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.

Да нет. В большинстве случаев он достаточен. Формат float обеспечивает представление десятичных цифр в двоичном коде, гарантируя 7 верных десятичных цифр. Это неопровержимый факт, который легко доказывается. Неопровержимым фактом также является то, что сумма этих чисел с точностью до 7 знака после точки/запятой, посчитанная вручную, равна 0,6333993.
Но самое интересное всегда происходит на границе. Число 0.6000006 является тем числом, в котором после обратной конвертации из двоичного кода в десятичный, после последней цифры 6 возникает «хвост» (или Ulps), который близок к числу 2. Этот самый Ulps и искажает результат суммирования наших чисел, т.к. во втором слагаемом мы имеем 7 значащих цифр и последняя цифра в нем больше или равно 4. После суммирования 2 и 4 получаем число 6, которое заставляет округлить нашу сумму с увеличением. Таким образом, при сложении двух десятичных чисел в двоичном коде, в формате float, можно гарантировать, что 6 значащих цифр результата будут верными.
2. умножение 0.06543455 на 139 — аналогично, при умножении необходимо иметь удвоенное количество разрядов для хранения результата.

Для хранения результата в формате float нам отведено всего 24 двоичных разряда, которые могут обеспечить точное представление 7 значащих десятичных цифр. Удвоенная мантисса нужна для промежуточных вычислений.
3. деление 131 на 0.066 аналогично предыдущему. 1/0.066=15.151515...=15.(15) имеет бесконечное количество значащих цифр.

Также как при умножении, на результат деления оказывает влияние хвост, который может искажать последнюю цифру результата. При делении несоизмеримых чисел всегда получается бесконечная дробь, которую необходимо округлить, чтобы как-то записать в отведенную разрядность. Как работать с десятичными дробями мы знаем. Но результат искажается за счет хвоста.

4. вычитание 105.3256 и 105.32 — здесь точности уже достаточно, и для получения корректного результата достаточно округлить результат до 4 цифр после запятой. Округление необходимо для отбрасывания погрешности, внесённой при преобразовании из 10-ого представления числа в двоичное.

А как вы догадались, что округлить надо до 4 цифр? Глядя на полученный результат? А почему не до 5 или 6 знака после запятой, как здесь кто-то выше предлагал?
Вообще, для проверки ваших математических выкладок я бы рекомендовал Вам использовать не Excel или калькулятор, а системы компьютерной алгебры.

Мои математические выкладки не зависят от инструмента, на котором их можно проверить. Они основаны на свойствах ЧПТ и только.
А как вы догадались, что округлить надо до 4 цифр? Глядя на полученный результат? А почему не до 5 или 6 знака после запятой, как здесь кто-то выше предлагал?

Можно и до 5 или 6, результат будет тот же. А до 4-х исходя из точности исходных данных, а именно 105.3256.
Вот тут-то вся и закавыка. При десятичных вычислениях все так и получается. Но мы, мало того, что ведем вычисления в двоичной арифметике, но еще и по правилам стандарта IEEE754. В котором обязательной процедурой является нормализация. И число 0.0056, после нормализации, будет выглядеть как 5.6*2^(-3)+Ulps в десятичном представлении. Округлите мантиссу до 4 знака после точки и что вы будете иметь?
http://ideone.com/Xwe4vV

И что же мы видим — исходная предпосылка для первого числа неверная — число 0.6000006 не существует в виде float, и из нее рождается демагогия.
Может подскажете тогда, какие еще исключения существуют в формате float?
0.6000006 = 1.001100110011001101001*2^(-1)= 0.6000006198883056640625
И чем же это число вам так не угодило?
1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.

Да нет. В большинстве случаев он достаточен. Формат float обеспечивает представление десятичных цифр в двоичном коде, гарантируя 7 верных десятичных цифр.

Вы как бы не учли, что если привести эти числа к одному порядку, получается уже 8 значащих цифр.
Вы путаете два понятия: количество знаков после запятой и количество значащих цифр. Когда мы число нормализуем, точность уже определяется через количество значащих цифр.
Мы вроде бы как нормализуем не число, а его представление. А число каким было, таким и осталось.
Минимальное нормализованное число, согласно спецификации стандарта, равно 1,17549435∙e-38. Пусть мы имеем число 1.23456*10^(-10). С какой точностью оно представлено? Если с точностью до 7 знака после точки, то оно равно 0. Если с точностью до 7 значащих цифр, то оно равно 1.23456*10^(-10).
Именно, Innotor просто мухлюет. Примерно так 0.13+0.13+0,1 ~= 0,4
Глубина вашей мысли меня потрясла.
В общем понятно, что Вы, с помощью некоторой ловкости рук, хотите объявить несостоятельным формат IEEE754 и предложить свою модификацию или даже новейшую разработку.

Будет интересно посмотреть.

Я недавно столкнулся с задачей реализовать стандартный набор С-функций math.h на ассемблере. В процессе чего выяснилось — что набор инструкций, удобный процессору, во многом перпендикулярен привычным человеку математическим функциям.

Соответственно, разрабатывая новый формат, Вам нужно спроектировать к нему и набор инструкций, эффективно с ним работающий.

Удачи.
См. опубликованные евразийские заявки на изобретение № 201500284 и 201500168.
Единственное, чем полезна эта статья, это напоминанием о шестнадцатеричном представлении ЧПТ, тем что Excel работает исключительно с float и об относительной точности вычислений, которую мы правда увидели уже только в обсуждении =)
Я рад, что вы что-то почерпнули из обсуждений. Но, к сожалению, в вашим коменте столько несуразицы, что мне просто жаль время это обсуждать.
Sign up to leave a comment.

Articles