При создании приложения, которое занимается множеством математических вычислений, я столкнулся с проблемой, заключающейся в том, что определенные числа вызывают ошибки округления.
Хотя я понимаю, что число с плавающей запятой не является точным , проблема заключается в том, как мне поступить с точными числами, чтобы убедиться, что при вычислениях на них округление с плавающей запятой не вызывает проблем?
distanceTraveled(startVel, duration, acceleration)
будет проверено.Ответы:
Существует три основных подхода к созданию альтернативных числовых типов, которые не имеют округления с плавающей запятой. Общая тема с ними заключается в том, что они используют целочисленную математику вместо этого различными способами.
Рациональные
Представьте число как целую часть и рациональное число с числителем и знаменателем. Число
15.589
будет представлено какw: 15; n: 589; d:1000
.При добавлении к 0,25 (то есть
w: 0; n: 1; d: 4
) это включает вычисление LCM, а затем добавление двух чисел. Это хорошо работает во многих ситуациях, хотя может привести к очень большим числам, когда вы работаете со многими рациональными числами, которые взаимно просты между собой.Фиксированная точка
У вас есть целая часть и десятичная часть. Все числа округлены (есть это слово - но вы знаете, где оно) с такой точностью. Например, вы могли бы иметь фиксированную точку с 3 десятичными знаками.
15.589
+0.250
становится добавлением589 + 250 % 1000
десятичной части (и затем любого переноса на целую часть). Это очень хорошо работает с существующими базами данных. Как уже упоминалось, есть округление, но вы знаете, где оно находится, и можете указать его так, чтобы оно было более точным, чем необходимо (вы измеряете только до 3 десятичных знаков, поэтому сделайте его фиксированным 4).Плавающая фиксированная точка
Сохраните значение и точность.
15.589
сохраняется как15589
для значения, так и3
для точности, а0.25
сохраняется как25
и2
. Это может обрабатывать произвольную точность. Я полагаю, что это то, что использует внутреннее устройство Java BigDecimal (не рассматривало его недавно). В какой-то момент вы захотите вернуть его из этого формата и отобразить его - и это может потребовать округления (опять же, вы контролируете, где оно находится).Как только вы определились с выбором для представления, вы можете либо найти существующие сторонние библиотеки, которые используют это, либо написать свою собственную. При написании своего собственного текста обязательно протестируйте его и убедитесь, что вы правильно выполняете математику.
источник
Если значения с плавающей запятой имеют проблемы с округлением, и вам не нужно сталкиваться с проблемами округления, из этого логически следует, что единственный способ - не использовать значения с плавающей запятой.
Теперь возникает вопрос: «Как мне сделать математику, включающую нецелые значения без переменных с плавающей запятой?» Ответ с типами данных произвольной точности . Расчеты медленнее, потому что они должны быть реализованы в программном обеспечении, а не в аппаратном обеспечении, но они точны. Вы не сказали, какой язык вы используете, поэтому я не могу рекомендовать пакет, но для большинства популярных языков программирования есть библиотеки произвольной точности.
источник
lot of mathematical calculations
не полезно, ни ответы даны. В подавляющем большинстве случаев (если вы не имеете дело с валютой), тогда float должно быть достаточно.Арифметика с плавающей точкой обычно довольно точна (15 десятичных цифр для a
double
) и довольно гибка. Проблемы возникают, когда вы занимаетесь математикой, что значительно уменьшает количество разрядов точности. Вот некоторые примеры:Отмена на вычитание:
1234567890.12345 - 1234567890.12300
результат0.0045
имеет только две десятичных цифры точности. Это поражает всякий раз, когда вы вычитаете два числа одинаковой величины.Глотание точности:
1234567890.12345 + 0.123456789012345
оценивается1234567890.24691
, последние десять цифр второго операнда теряются.Умножение: если вы умножите два 15-значных чисел, результат будет иметь 30 цифр, которые необходимо сохранить. Но вы не можете хранить их, поэтому последние 15 бит потеряны. Это особенно утомительно, когда в сочетании с
sqrt()
(как вsqrt(x*x + y*y)
: Результат будет иметь только 7,5 цифр точности.Это основные подводные камни, о которых вам нужно знать. И как только вы узнаете о них, вы можете попытаться сформулировать свою математику таким образом, чтобы избежать их. Например, если вам нужно увеличивать значение снова и снова в цикле, избегайте этого:
После нескольких итераций большая
f
часть поглотит часть точностиdf
. Хуже того, ошибки будут накапливаться, что приведет к противоречивой ситуации, что меньшееdf
может привести к худшим общим результатам. Лучше напиши это:Поскольку вы комбинируете приращения в одном умножении, полученное значение
f
будет с точностью до 15 десятичных цифр.Это только пример, есть другие способы избежать потери точности по другим причинам. Но это уже очень помогает задуматься о величине вовлеченных значений и представить, что произойдет, если вы будете делать математику с ручкой и бумагой, округляя до фиксированного числа цифр после каждого шага.
источник
Как убедиться, что у вас нет проблем: узнайте о арифметических задачах с плавающей точкой, или наймите кого-нибудь, кто имеет, или используйте здравый смысл.
Первая проблема - точность. Во многих языках у вас есть «float» и «double» (двойное положение для «двойной точности»), и во многих случаях «float» дает вам точность около 7 цифр, в то время как double дает вам 15. Здравый смысл заключается в том, что если у вас есть В ситуации, когда точность может быть проблемой, 15 цифр намного лучше, чем 7 цифр. Во многих слегка проблемных ситуациях использование «double» означает, что вам это сойдет с рук, а «float» означает, что вы этого не сделаете. Допустим, рыночная капитализация компании составляет 700 миллиардов долларов. Представьте это в формате с плавающей запятой, и младший бит составляет $ 65536. Представьте его, используя double, а младший бит составляет около 0,012 цента. Поэтому, если вы действительно, действительно не знаете, что делаете, вы используете double, а не float.
Вторая проблема в большей степени принципиальная. Если вы делаете два разных вычисления, которые должны давать один и тот же результат, они часто этого не делают из-за ошибок округления. Два результата, которые должны быть равны, будут «почти равны». Если два результата близки друг к другу, то реальные значения могут быть равны. Или они могут не быть. Вы должны помнить об этом и должны писать и использовать функции, которые говорят «x определенно больше, чем y» или «x определенно меньше, чем y» или «x и y могут быть равны».
Эта проблема становится намного хуже, если вы используете округление, например, «округлить x до ближайшего целого числа». Если вы умножите 120 * 0,05, результат должен быть 6, но вы получите «некоторое число, очень близкое к 6». Если затем вы «округлите до ближайшего целого числа», это «число, очень близкое к 6», может быть «немного меньше 6» и округлено до 5. И обратите внимание, что не имеет значения, насколько точна ваша точность. Не имеет значения, насколько близок к 6 ваш результат, если он меньше 6.
И в-третьих, некоторые проблемы сложны . Это означает, что нет простого и быстрого правила. Если ваш компилятор поддерживает «long double» с большей точностью, вы можете использовать «long double» и посмотреть, имеет ли это значение. Если это не имеет значения, то либо у вас все в порядке, либо у вас есть действительно сложная проблема. Если это имеет значение, которое вы ожидаете (например, изменение с 12-го знака после запятой), то вы, вероятно, в порядке. Если это действительно меняет ваши результаты, то у вас есть проблема. Просить помощи.
источник
Большинство людей совершают ошибку, когда видят двойное, они кричат BigDecimal, хотя на самом деле они просто перенесли проблему в другое место. Double дает знак бита: 1 бит, экспонента ширина: 11 бит. Значимость и точность: 53 бита (52 явно хранятся). Из-за двойного характера, чем больше целое число, вы теряете относительную точность. Для расчета относительной точности мы используем здесь ниже.
Относительная точность удвоения в расчете мы используем следующую foluma 2 ^ E <= abs (X) <2 ^ (E + 1)
epsilon = 2 ^ (E-10)% для 16-разрядного числа с плавающей запятой (половина точности)
Другими словами, если вы хотите Точность +/- 0,5 (или 2 ^ -1), максимальный размер, который может быть числом, составляет 2 ^ 52. Любое значение больше этого, а расстояние между числами с плавающей точкой больше 0,5.
Если вы хотите получить точность +/- 0,0005 (около 2 ^ -11), максимальный размер, который может быть указан, равен 2 ^ 42. Любое значение больше этого, а расстояние между числами с плавающей запятой больше 0,0005.
Я не могу дать лучший ответ, чем этот. Пользователю необходимо выяснить, какой точности они хотят при выполнении необходимых вычислений, и их единице измерения (метры, футы, дюймы, мм, см). В подавляющем большинстве случаев поплавка будет достаточно для простого моделирования в зависимости от масштаба мира, который вы хотите симулировать.
Хотя это то, что нужно сказать, если вы стремитесь симулировать мир размером 100 на 100 метров, у вас будет где-то порядок порядка 2 ^ -45. Это даже не относится к тому, как современные FPU внутри процессоров будут выполнять вычисления вне собственного размера шрифта, и только после завершения вычисления они будут округлять (в зависимости от режима округления FPU) до собственного размера шрифта.
источник