Только в одном случае: когда последовательность девяток после запятой бесконечна. А в примере, который вы комментируете, последовательность девяток конечна, и обусловлена типом данных, в рамках которого были сделаны расчёты.
Я снова повторюсь, что не считаю вас правым в этой ситуации, хотя и не возражаю против того, чтобы у вас было своё мнение. Я описываю тему, связанную с арифметикой с плавающей запятой, и считаю что читатель должен владеть основой, если его эта тема волнует. Если это не его тема, значит ему не нужно её читать. Вот представьте, вы оказались в вузе, скажем, на математическом факультете. Преподаватель математического анализа исходит из предположения, что вы знаете что такое 2+2=4. А вы встаёте и возражаете: никто не пойдёт читать 100500 источников, чтобы вас понять. Чья это проблема: ваша или преподавательская? Очевидно, что ваша. Вы могли бы с тем же успехом попросить меня разъяснить в двух словах что такое вообще сложение чисел, как возникает перенос, иначе читатели могут не понять. Потому что моя целевая аудитория — это люди, которые УЖЕ давно поняли, что если они имеют дело с числами с плавающей запятой, то они обязаны понимать их также, как понимают целые числа. Если они этого по какой-то причине не делают, значит дальше погружаться в тему им НЕ нужно.
Считайте, что я в начале статьи сделал #include "floating point feeling" и не должен повторять своими словами то, что описано в этой библиотеке. Ну а если такой библиотеки нет у читателя, то и пара слов об устройстве формата binary64 НИЧЕГО не даст, а только утомит тех, кто в теме.
Я думаю, что изложил свои аргументы и далее повторяться не буду, уж простите.
Это именно видео-курс. Текстовых данных полно в интернете, или в книге [1]. В моём исполнении текстовый курс будет стоить в 10 раз дороже, чем видео. Но мне кажется, что вы здесь немного ошибаетесь. Вот смотрите сами: текста в интернете полно, а учить никто не хочет, ленятся читать. Вот это видео как раз для ленивых :) Потому что я нашёл способ очень простого и понятного объяснения темы, и в тексте оно как-то не будет смотреться. Но это моё мнение.
Благодарю за замечание, но я не могу с вами согласиться. Я писал именно эту статью не для практически любого читателя хабра, а для того, которому не нужно пояснять азы арифметики с плавающей запятой. Для тех, кому эти азы нужны, я предложил сначала ознакомиться с литературой [3-5]. Там всё это уже есть, зачем мне повторять, что 2+2=4? Мне в каждой из сотни статей, что я задумал по этой теме повторять эту простую истину и объяснять про мантиссу, экспоненту и всё прочее? Поверьте, парой предложений тут не обойтись. Скажешь А, сразу попросят сказать Б… и вот тут понеслось, создавая учебный курс по данной теме, пришлось делать аж 8 уроков, и то это лишь первая часть. И делал я их как раз для того, чтобы мне не пришлось повторять одно и то же.
Нет, для сложения n чисел типа double есть другие алгоритмы (о них в другой статье будет), которые призваны не убрать полностью, но уменьшить погрешность вычислений (чтобы убрать полностью именно этим алгоритмом, то да, нужно, грубо говоря, ещё n чисел, и смысла в таком алгоритме уже нет). Если же нужно убрать её полностью для какой-то реальной задачи, то следует либо перейти к рациональным числам с произвольной точностью, либо к арифметике с плавающей запятой с произвольной точностью.
Аплодирую! Вы почти правильно поняли то, что я хотел сказать по этой дискуссии. Кроме последнего абзаца. Выбрать способ представления двух 32-х битных чисел в виде суммы двух 32-х битных чисел лучше так, чтобы результат сложения удовлетворял условиям задачи, ради которой эта сумма ищется. В одной задаче лучше и правда ничего не делать, в другой может оказаться, что это и не лучше вовсе. Всё зависит от задачи, а не от желаний программиста.
Например, когда мы реализуем сложение в длинной арифметике, то в одном из чисел хранится сумма, а в другом — перенос в старший разряд.
В остальном вы правы, да я абсолютизирую, потому что критически отношусь к своему труду и от других того же требую, может и напрасно это, но такой уж я человек. Поэтому хотя конвертирование десятичного числа в плавающее хоть и разрешено с этой ошибкой в последнем бите делать, я всё-таки от своих требую строгой точности. Причины может быть личные, может быть в психике моей, но так уже вышло :)
Наука на Open Source не совсем вся переползает. Например, многие используют Maple (закрытый) для своей работы и некоторые его функции настолько сильнее чем у других (бесплатных) систем компьютерной алгебры реализованы, что тем до Maple ещё как до Луны. И я не верю, что это можно сделать (перейти на OpenSource), пока в нашем обществе деньги в принципе как явление существуют. Но это уже философский вопрос. Сам я против Open Source, но ни коим образом других людей не осуждаю за то, что деньги зарабатывают, на бесплатном софте сидя. Право каждого, не моё дело.
Да, Кланг у нас старой версии, не спорю. Но не только в этом конвертировании проблема. Абсолютно все компиляторы, которые нам доступны, содержат сотни (это не преувеличение) других ошибок, гораздо более серьёзных, но разглашать их мне пока запрещено.
В случае работы с числами, близкими к единице они хорошо себя показывают. Но вообще я их просто так в этом списке упомянул, чтобы пояснить собеседнику, что я в курсе существования разных типов данных, в том числе весьма экзотических.
Нет, quire аккумуляторы там не работают, ошибка была ведь не в том, что мы в результате каких-то накоплений потеряли точность, а именно в результате ошибок в единичных операциях с очень маленькими числами. Плотность маленьких чисел у позитов настолько низка по сравнению с плотностью денормализованных чисел в IEEE-754, что это и неудивительно. А если прямо совсем всё делать в quire, то тогда смысла нет было создавать операции сложения и вычитания для обычных позитов. Но они ведь зачем-то есть, и они работают плохо в задачах математического анализа. В этом проблема. Не знаю как для других исследователей, для нас это однозначный приговор позитам. По крайней мере, на сегодня точно. Если нужна хорошая точность, мы возьмём float128, например.
Мне кажется, уходить в «бесконечно-малые» и «бесконечно-большие» на posit просто нельзя
Да, нельзя. В этом и проблема. И инструмент quire задачу решает не лучше, чем если вместо double я возьму float128, например, или, если будет нужно, более крупный тип данных.
Мне кажется, что это полезный ответ, в том смысле, что если бы те же вычисления делались с применением обычной арифметики аналогичной разрядности, то погрешность результата была бы «бесконечной».
Нет, как раз в случае с обычной арифметикой там было бы вполне обычное число, близкое к правильному ответу.
Я понял о чём вы, но вы не понимаете о чём я. Я о том, что причина неравенства НЕ В ТОМ, что функция BIN работает с потерей, а в том, что СУММА работает с потерей. Думаю, я всё объяснил, вы уж простите.
Примеры не дам, исследования нашего научного центра не подлежат разглашению. Могу только сказать одно — они не работают правильно в задачах, связанных с исчислением бесконечно малых. Мы завалили их на простых тестах, где нужно было: интегрировать, решать дифференциальное уравнение численно, применять другие численные методы, где нужно было работать с бесконечно-малыми (имеется в виду, конечно, просто с очень маленькими числами). Алгоритмы, которые хорошо сходятся для чисел формата IEEE-754 на позитах не сходились и уходили в бесконечность, либо плясали в широком диапазоне около ожидаемого ответа.
Интервальная арифметика часто выдает очень правильный, но бесполезный ответ, например, что число будет в интервале от 0 до бесконечности. Далее, возникают интересные ситуации, когда интервал [A, B] нужно поделить на интервал [A, B] (ответ, естественно, не единица), особенно когда A и B имеют разные знаки. Больше сказать ничего не могу, увы.
Разложите в двоичный вид и увидите, почему будет округление. Именно ЭТО округление и есть причина описываемой в статье ситуации. Если бы его не было, то условие if (0.1+0.2==0.3) срабатывало бы всегда, даже если эти числа сами по себе точно не могут быть представлены в двоичной арифметике.
Я не знаю как работают компиляторы, исходников у меня нет, но знаю о том, почему так получается. Дело в том, что есть так называемые пограничные случаи, когда десятичное число очень-очень близко к середине между двух точно-представимых. Так близко, что разница проявляется только в каком-нибудь тысячном бите после запятой. Каким бы ни был алгоритм конвертирования, если он работает с фиксированным числом битов, он будет завален каким-нибудь таким тестом. Поэтому делаются эти тесты очень просто. Берём число, лежащее точно посередине. Прибавляем к нему (или вычитаем из него), например, 2 в степени минус тысяча (десять тысяч, миллион) — и всё, тест готов. Промежуточные округления тут роли не играют, причина ошибки — недостаток битов в процессе вычисления, алгоритм не может охватить все тысячу битов, чтобы увидеть направление округления. Нужна длинная арифметика.
По поводу Clang, там я точно уже не помню, но он на long double у меня легко заваливался, сейчас уже не скажу, какие тесты были.
Пример на то и пример, чтобы проиллюстрировать проблему, но не описать всё её многообразие проявлений. Более того, я пояснил, что статья для тех кто в теме, кому не нужно пояснять что такое 0.1+0.2 и почему это не равно 0.3. В этом примере причина ошибки НЕ В ТОМ, что числа на грани, а в том, что в результате сложения происходит округление суммы. Разложите, пожалуйста, эти числа на двоичное представление, сложите руками на бумаге и увидите, почему происходит округление. В учебном курсе [5] даётся подробное разъяснение именно этого примера в каком-то уроке, не хотелось бы здесь делать учебную комнату, уж прошу меня простить :)
Я вам больше скажу, почти все компиляторы не умеют переводить десятичное число в двоичное правильно. Кроме GCC последних версий. Все остальные, что были доступны, были успешно нами завалены. Когда число очень близко к границе округления, компилятору не хватает внутренней точности чтобы округлить правильно. Например, вот это число:
float x = 1.26765082690182057923967346278399
99999999999999999999999999999999999999999999
99999999999999999999999999999999999999999999
99999999999999999999999999999999999999999999
9999999999999999999e30f;
Правильный ответ 0x71800001. Компиляторы Visual C++ и Intel C++ (любых версий) выдают на один бит больше. Таких примеров у нас сотни.
Указанная в статье проблема, связанная с тем, что при сложении чисел с плавающей запятой возникает погрешность, характерна для любых целых систем счисления. В книге [1], на которую я ссылаюсь, теоремы доказываются именно для произвольной системы.
Я говорю о том, чтобы делать указанным способом именно промежуточные вычисления, а выдавать уже с округлением. Накопившиеся именно в ходе выдачи округления распределять по закону, но я законов этих не знаю. Так что по остальной части вопроса — это не ко мне :)
Мне известно только два способа: рациональные числа и арифметика с произвольной точностью (увеличиваем точность так, чтобы эти копейки, даже возведённые в степень, не сыграли бы роли). Если бы я разрабатывал финансовое ПО, то смотрел бы в сторону библиотеки длинной арифметики MPIR и там есть соответствующие типы данных mpq (рациональные) и mpf (с плавающей запятой). Слишком сложных вычислений там быть не может, поэтому каких-то особенных тормозов не будет.
Тут можно ещё упереться в нарушение закона. Дело в том, что если где-то прописано, что процентная ставка 0.1%, то при временном переводе её в double, мы получаем другую ставку, и нарушаем закон. Так что здесь нужно сначала доказать, что такой перевод повлияет только на скорость, но не на ответ.
Только в одном случае: когда последовательность девяток после запятой бесконечна. А в примере, который вы комментируете, последовательность девяток конечна, и обусловлена типом данных, в рамках которого были сделаны расчёты.
Я снова повторюсь, что не считаю вас правым в этой ситуации, хотя и не возражаю против того, чтобы у вас было своё мнение. Я описываю тему, связанную с арифметикой с плавающей запятой, и считаю что читатель должен владеть основой, если его эта тема волнует. Если это не его тема, значит ему не нужно её читать. Вот представьте, вы оказались в вузе, скажем, на математическом факультете. Преподаватель математического анализа исходит из предположения, что вы знаете что такое 2+2=4. А вы встаёте и возражаете: никто не пойдёт читать 100500 источников, чтобы вас понять. Чья это проблема: ваша или преподавательская? Очевидно, что ваша. Вы могли бы с тем же успехом попросить меня разъяснить в двух словах что такое вообще сложение чисел, как возникает перенос, иначе читатели могут не понять. Потому что моя целевая аудитория — это люди, которые УЖЕ давно поняли, что если они имеют дело с числами с плавающей запятой, то они обязаны понимать их также, как понимают целые числа. Если они этого по какой-то причине не делают, значит дальше погружаться в тему им НЕ нужно.
Считайте, что я в начале статьи сделал #include "floating point feeling" и не должен повторять своими словами то, что описано в этой библиотеке. Ну а если такой библиотеки нет у читателя, то и пара слов об устройстве формата binary64 НИЧЕГО не даст, а только утомит тех, кто в теме.
Я думаю, что изложил свои аргументы и далее повторяться не буду, уж простите.
Это именно видео-курс. Текстовых данных полно в интернете, или в книге [1]. В моём исполнении текстовый курс будет стоить в 10 раз дороже, чем видео. Но мне кажется, что вы здесь немного ошибаетесь. Вот смотрите сами: текста в интернете полно, а учить никто не хочет, ленятся читать. Вот это видео как раз для ленивых :) Потому что я нашёл способ очень простого и понятного объяснения темы, и в тексте оно как-то не будет смотреться. Но это моё мнение.
Благодарю за замечание, но я не могу с вами согласиться. Я писал именно эту статью не для практически любого читателя хабра, а для того, которому не нужно пояснять азы арифметики с плавающей запятой. Для тех, кому эти азы нужны, я предложил сначала ознакомиться с литературой [3-5]. Там всё это уже есть, зачем мне повторять, что 2+2=4? Мне в каждой из сотни статей, что я задумал по этой теме повторять эту простую истину и объяснять про мантиссу, экспоненту и всё прочее? Поверьте, парой предложений тут не обойтись. Скажешь А, сразу попросят сказать Б… и вот тут понеслось, создавая учебный курс по данной теме, пришлось делать аж 8 уроков, и то это лишь первая часть. И делал я их как раз для того, чтобы мне не пришлось повторять одно и то же.
Нет, для сложения n чисел типа double есть другие алгоритмы (о них в другой статье будет), которые призваны не убрать полностью, но уменьшить погрешность вычислений (чтобы убрать полностью именно этим алгоритмом, то да, нужно, грубо говоря, ещё n чисел, и смысла в таком алгоритме уже нет). Если же нужно убрать её полностью для какой-то реальной задачи, то следует либо перейти к рациональным числам с произвольной точностью, либо к арифметике с плавающей запятой с произвольной точностью.
Аплодирую! Вы почти правильно поняли то, что я хотел сказать по этой дискуссии. Кроме последнего абзаца. Выбрать способ представления двух 32-х битных чисел в виде суммы двух 32-х битных чисел лучше так, чтобы результат сложения удовлетворял условиям задачи, ради которой эта сумма ищется. В одной задаче лучше и правда ничего не делать, в другой может оказаться, что это и не лучше вовсе. Всё зависит от задачи, а не от желаний программиста.
Например, когда мы реализуем сложение в длинной арифметике, то в одном из чисел хранится сумма, а в другом — перенос в старший разряд.
Я имел в виду нету исходников Intel С++ И MS C++.
В остальном вы правы, да я абсолютизирую, потому что критически отношусь к своему труду и от других того же требую, может и напрасно это, но такой уж я человек. Поэтому хотя конвертирование десятичного числа в плавающее хоть и разрешено с этой ошибкой в последнем бите делать, я всё-таки от своих требую строгой точности. Причины может быть личные, может быть в психике моей, но так уже вышло :)
Наука на Open Source не совсем вся переползает. Например, многие используют Maple (закрытый) для своей работы и некоторые его функции настолько сильнее чем у других (бесплатных) систем компьютерной алгебры реализованы, что тем до Maple ещё как до Луны. И я не верю, что это можно сделать (перейти на OpenSource), пока в нашем обществе деньги в принципе как явление существуют. Но это уже философский вопрос. Сам я против Open Source, но ни коим образом других людей не осуждаю за то, что деньги зарабатывают, на бесплатном софте сидя. Право каждого, не моё дело.
Да, Кланг у нас старой версии, не спорю. Но не только в этом конвертировании проблема. Абсолютно все компиляторы, которые нам доступны, содержат сотни (это не преувеличение) других ошибок, гораздо более серьёзных, но разглашать их мне пока запрещено.
В случае работы с числами, близкими к единице они хорошо себя показывают. Но вообще я их просто так в этом списке упомянул, чтобы пояснить собеседнику, что я в курсе существования разных типов данных, в том числе весьма экзотических.
Нет, quire аккумуляторы там не работают, ошибка была ведь не в том, что мы в результате каких-то накоплений потеряли точность, а именно в результате ошибок в единичных операциях с очень маленькими числами. Плотность маленьких чисел у позитов настолько низка по сравнению с плотностью денормализованных чисел в IEEE-754, что это и неудивительно. А если прямо совсем всё делать в quire, то тогда смысла нет было создавать операции сложения и вычитания для обычных позитов. Но они ведь зачем-то есть, и они работают плохо в задачах математического анализа. В этом проблема. Не знаю как для других исследователей, для нас это однозначный приговор позитам. По крайней мере, на сегодня точно. Если нужна хорошая точность, мы возьмём float128, например.
Да, нельзя. В этом и проблема. И инструмент quire задачу решает не лучше, чем если вместо double я возьму float128, например, или, если будет нужно, более крупный тип данных.
Нет, как раз в случае с обычной арифметикой там было бы вполне обычное число, близкое к правильному ответу.
Я понял о чём вы, но вы не понимаете о чём я. Я о том, что причина неравенства НЕ В ТОМ, что функция BIN работает с потерей, а в том, что СУММА работает с потерей. Думаю, я всё объяснил, вы уж простите.
Ну вот кто не ждёт, а действует, тот уже проверил, где это работать может.
Примеры не дам, исследования нашего научного центра не подлежат разглашению. Могу только сказать одно — они не работают правильно в задачах, связанных с исчислением бесконечно малых. Мы завалили их на простых тестах, где нужно было: интегрировать, решать дифференциальное уравнение численно, применять другие численные методы, где нужно было работать с бесконечно-малыми (имеется в виду, конечно, просто с очень маленькими числами). Алгоритмы, которые хорошо сходятся для чисел формата IEEE-754 на позитах не сходились и уходили в бесконечность, либо плясали в широком диапазоне около ожидаемого ответа.
Интервальная арифметика часто выдает очень правильный, но бесполезный ответ, например, что число будет в интервале от 0 до бесконечности. Далее, возникают интересные ситуации, когда интервал [A, B] нужно поделить на интервал [A, B] (ответ, естественно, не единица), особенно когда A и B имеют разные знаки. Больше сказать ничего не могу, увы.
Я имею в виду, возьмите соответствующие ТОЧНЫЕ значения (которые умещаются в double без округления), указанные в статье:
Разложите в двоичный вид и увидите, почему будет округление. Именно ЭТО округление и есть причина описываемой в статье ситуации. Если бы его не было, то условие if (0.1+0.2==0.3) срабатывало бы всегда, даже если эти числа сами по себе точно не могут быть представлены в двоичной арифметике.
Я не знаю как работают компиляторы, исходников у меня нет, но знаю о том, почему так получается. Дело в том, что есть так называемые пограничные случаи, когда десятичное число очень-очень близко к середине между двух точно-представимых. Так близко, что разница проявляется только в каком-нибудь тысячном бите после запятой. Каким бы ни был алгоритм конвертирования, если он работает с фиксированным числом битов, он будет завален каким-нибудь таким тестом. Поэтому делаются эти тесты очень просто. Берём число, лежащее точно посередине. Прибавляем к нему (или вычитаем из него), например, 2 в степени минус тысяча (десять тысяч, миллион) — и всё, тест готов. Промежуточные округления тут роли не играют, причина ошибки — недостаток битов в процессе вычисления, алгоритм не может охватить все тысячу битов, чтобы увидеть направление округления. Нужна длинная арифметика.
По поводу Clang, там я точно уже не помню, но он на long double у меня легко заваливался, сейчас уже не скажу, какие тесты были.
Пример на то и пример, чтобы проиллюстрировать проблему, но не описать всё её многообразие проявлений. Более того, я пояснил, что статья для тех кто в теме, кому не нужно пояснять что такое 0.1+0.2 и почему это не равно 0.3. В этом примере причина ошибки НЕ В ТОМ, что числа на грани, а в том, что в результате сложения происходит округление суммы. Разложите, пожалуйста, эти числа на двоичное представление, сложите руками на бумаге и увидите, почему происходит округление. В учебном курсе [5] даётся подробное разъяснение именно этого примера в каком-то уроке, не хотелось бы здесь делать учебную комнату, уж прошу меня простить :)
Я вам больше скажу, почти все компиляторы не умеют переводить десятичное число в двоичное правильно. Кроме GCC последних версий. Все остальные, что были доступны, были успешно нами завалены. Когда число очень близко к границе округления, компилятору не хватает внутренней точности чтобы округлить правильно. Например, вот это число:
Правильный ответ 0x71800001. Компиляторы Visual C++ и Intel C++ (любых версий) выдают на один бит больше. Таких примеров у нас сотни.
Указанная в статье проблема, связанная с тем, что при сложении чисел с плавающей запятой возникает погрешность, характерна для любых целых систем счисления. В книге [1], на которую я ссылаюсь, теоремы доказываются именно для произвольной системы.
Я говорю о том, чтобы делать указанным способом именно промежуточные вычисления, а выдавать уже с округлением. Накопившиеся именно в ходе выдачи округления распределять по закону, но я законов этих не знаю. Так что по остальной части вопроса — это не ко мне :)
Мне известно только два способа: рациональные числа и арифметика с произвольной точностью (увеличиваем точность так, чтобы эти копейки, даже возведённые в степень, не сыграли бы роли). Если бы я разрабатывал финансовое ПО, то смотрел бы в сторону библиотеки длинной арифметики MPIR и там есть соответствующие типы данных mpq (рациональные) и mpf (с плавающей запятой). Слишком сложных вычислений там быть не может, поэтому каких-то особенных тормозов не будет.
Тут можно ещё упереться в нарушение закона. Дело в том, что если где-то прописано, что процентная ставка 0.1%, то при временном переводе её в double, мы получаем другую ставку, и нарушаем закон. Так что здесь нужно сначала доказать, что такой перевод повлияет только на скорость, но не на ответ.