Прежде всего, значения с плавающей запятой не являются «случайными» в своем поведении. Точное сравнение может иметь смысл в реальных ситуациях. Но если вы собираетесь использовать число с плавающей запятой, вам нужно знать, как оно работает. Ошибка в предположении, что числа с плавающей точкой работают как действительные числа, приведут к быстрому взлому кода. Ошибка в предположении, что с результатами с плавающей запятой связан большой случайный размытость (как предлагает большинство ответов здесь), и вы получите код, который сначала работает, но в конечном итоге приводит к ошибкам большой величины и ошибкам.
Прежде всего, если вы хотите программировать с плавающей запятой, вы должны прочитать это:
Что каждый компьютерщик должен знать об арифметике с плавающей точкой
Да, прочитайте все это. Если это слишком обременительно, вы должны использовать целые числа / фиксированную точку для своих расчетов, пока у вас не будет времени прочитать их. :-)
Теперь, с учетом сказанного, самые большие проблемы с точными сравнениями с плавающей точкой сводятся к:
Тот факт, что многие значения, которые вы можете записать в источнике или прочитать с помощью scanf
или strtod
, не существуют как значения с плавающей запятой и автоматически преобразуются в ближайшее приближение. Об этом говорил ответ demon9733.
Тот факт, что многие результаты округляются из-за отсутствия достаточной точности для представления фактического результата. Простой пример, где вы можете увидеть это добавление x = 0x1fffffe
и y = 1
плавание. Здесь он x
имеет 24 бита точности в мантиссе (хорошо) и y
имеет всего 1 бит, но когда вы добавляете их, их биты не находятся в перекрывающихся местах, и результат должен будет иметь 25 бит точности. Вместо этого он округляется ( 0x2000000
в режиме округления по умолчанию).
Тот факт, что многие результаты округляются из-за необходимости бесконечного количества мест для правильного значения. Это включает в себя как рациональные результаты, такие как 1/3 (с которым вы знакомы из десятичной дроби, где она занимает бесконечно много мест), так и 1/10 (которая также занимает бесконечно много мест в двоичной системе, поскольку 5 не является степенью 2), а также иррациональные результаты, такие как квадратный корень всего, что не является идеальным квадратом.
Двойное округление. В некоторых системах (в частности, в x86) выражения с плавающей запятой оцениваются с большей точностью, чем их номинальные типы. Это означает, что когда происходит один из указанных выше типов округления, вы получите два шага округления: сначала округление результата до типа с более высокой точностью, затем округление до конечного типа. В качестве примера рассмотрим, что происходит в десятичном виде, если округлить 1.49 до целого числа (1), а не в том, что происходит, если сначала округлить его до одного десятичного знака (1.5), а затем округлить полученный результат до целого числа (2). На самом деле это одна из самых неприятных областей в плавающей точке, поскольку поведение компилятора (особенно для глючных, не соответствующих стандарту компиляторов, таких как GCC) непредсказуемо.
Трансцендентные функции ( trig
, exp
, log
и т.д.) не определены , чтобы правильно округленные результаты; результат только что указан, чтобы быть правильным в пределах одной единицы в последнем месте точности (обычно упоминаемый как 1ulp ).
Когда вы пишете код с плавающей запятой, вам нужно помнить о том, что вы делаете с числами, которые могут привести к неточности результатов, и делать соответствующие сравнения. Часто имеет смысл сравнивать с «эпсилоном», но этот эпсилон должен основываться на величине сравниваемых чисел , а не на абсолютной константе. (В случаях, когда сработает абсолютная постоянная эпсилон, это сильно указывает на то, что фиксированная точка, а не с плавающей точкой, является правильным инструментом для работы!)
Изменить: В частности, проверка эпсилон-относительной величины должна выглядеть примерно так:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Где FLT_EPSILON
находится константа float.h
(замените ее DBL_EPSILON
на double
s или LDBL_EPSILON
для long double
s), и K
вы выбираете такую константу, чтобы накопленная ошибка ваших вычислений была определенно ограничена K
единицами в последнем месте (и если вы не уверены, что получили ошибку связанный расчет правильно, сделайте K
в несколько раз больше, чем ваши вычисления говорят, что это должно быть).
Наконец, обратите внимание, что если вы используете это, может потребоваться некоторая особая осторожность вблизи нуля, так FLT_EPSILON
как не имеет смысла для ненормированных. Быстрое решение было бы сделать это:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
и аналогично заменить, DBL_MIN
если использовать удваивается.
fabs(x+y)
проблематично, еслиx
иy
(может) иметь другой знак. Тем не менее, хороший ответ против потока культовых сравнений.x
иy
есть разные признаки, это не проблема. Правая будет «слишком мал», но такx
иy
имеют разный знак, они не должны сравнивать равные в любом случае. (Если они не настолько малы, чтобы быть denormal, но тогда второй ловит случае это)Поскольку 0 точно представляется в виде числа с плавающей точкой IEEE754 (или с использованием любой другой реализации чисел fp, с которой я когда-либо работал), сравнение с 0, вероятно, безопасно. Однако вы можете быть укушены, если ваша программа вычисляет значение (такое как
theView.frame.origin.x
), которое, по вашему мнению, должно быть равно 0, но которое ваши вычисления не могут гарантировать равным 0.Чтобы уточнить немного, вычисление, такое как:
(если ваш язык или система не сломаны) создаст значение, такое что (areal == 0.0) вернет true, но другое вычисление, такое как
может нет.
Если вы можете быть уверены, что ваши вычисления дают значения, равные 0 (а не только то, что они дают значения, которые должны быть равны 0), тогда вы можете пойти дальше и сравнить значения fp с 0. Если вы не можете убедиться в необходимой степени Лучше всего придерживаться обычного подхода «терпимого равенства».
В худших случаях небрежное сравнение значений fp может быть чрезвычайно опасным: подумайте об авионике, наведении оружия, работе силовой установки, навигации транспортного средства, практически в любом приложении, в котором вычисления встречаются в реальном мире.
Для Angry Birds не так уж и опасно.
источник
1.30 - 2*(0.65)
это прекрасный пример выражения, которое, очевидно, оценивается в 0.0, если ваш компилятор реализует IEEE 754, потому что числа, представленные как0.65
и1.30
имеющие одинаковые значения, и умножение на два, очевидно, является точным.Я хочу дать немного другой ответ, чем другие. Они отлично подходят для ответа на ваш вопрос, как указано, но, вероятно, не для того, что вам нужно знать или какова ваша настоящая проблема.
С плавающей точкой в графике все в порядке! Но нет необходимости сравнивать поплавки напрямую. Зачем вам это нужно? Графика использует поплавки для определения интервалов. И сравнение, если поплавок находится в интервале, также определяемом поплавками, всегда хорошо определено и просто должно быть последовательным, не точным или точным! Пока пиксель (который также является интервалом!) Может быть назначен, это все графические потребности.
Так что, если вы хотите проверить, находится ли ваша точка вне диапазона [0..width [, это нормально. Просто убедитесь, что вы определяете включение последовательно. Например, всегда определяйте внутри is (x> = 0 && x <width). То же самое касается тестов на пересечение или попадание.
Однако, если вы злоупотребляете графической координатой как своего рода флагом, как, например, чтобы увидеть, пристыковано ли окно или нет, вы не должны этого делать. Вместо этого используйте логический флаг, который отделен от слоя графического представления.
источник
Сравнение с нулем может быть безопасной операцией, если ноль не является расчетным значением (как отмечено в ответе выше). Причина этого в том, что ноль - это отлично представимое число в плавающей точке.
Говоря о идеально представимых значениях, вы получаете 24-битный диапазон в представлении степени двух (одинарная точность). Таким образом, 1, 2, 4 отлично представимы, как .5, .25 и .125. Пока все ваши важные биты в 24-битах, вы золотой. Таким образом, 10.625 могут быть представлены точно.
Это здорово, но под давлением быстро развалится. На ум приходят два сценария: 1) Когда происходит расчет. Не верьте этому sqrt (3) * sqrt (3) == 3. Такого не будет. И это, вероятно, не будет в эпсилоне, как предполагают некоторые другие ответы. 2) Когда задействован любой не-сила-2 (NPOT). Так что это может звучать странно, но 0.1 - это бесконечный ряд в двоичном коде, и поэтому любые вычисления с таким числом будут неточными с самого начала.
(Да, и в первоначальном вопросе упоминалось сравнение с нулем. Не забывайте, что -0.0 также является совершенно допустимым значением с плавающей запятой.)
источник
«Правильный ответ» закрывает выбор
K
. ВыборK
заканчивается так же, как выбор,VISIBLE_SHIFT
но выборK
менее очевиден, потому что, в отличие отVISIBLE_SHIFT
него, он не основан ни на каком свойстве отображения. Таким образом, выберите свой яд - выберитеK
или выберитеVISIBLE_SHIFT
. Этот ответ призывает к выбору,VISIBLE_SHIFT
а затем демонстрирует трудности выбораK
]Именно из-за ошибок округления не следует использовать сравнение «точных» значений для логических операций. В вашем конкретном случае позиции на визуальном дисплее не может иметь значения, будет ли позиция 0,0 или 0,0000000003 - разница невидима для глаза. Так что ваша логика должна быть примерно такой:
Однако, в конце концов, «невидимый для глаз» будет зависеть от ваших свойств дисплея. Если вы можете верхнюю границу дисплея (вы должны быть в состоянии); затем выберите
VISIBLE_SHIFT
часть этой верхней границы.Теперь «правильный ответ» опирается на
K
так что давайте рассмотрим выборK
. «Правильный ответ» выше говорит:Так что нам нужно
K
. Если получитьK
более сложно, менее интуитивно, чем выбрать мой,VISIBLE_SHIFT
тогда вы решите, что работает для вас. Чтобы найти,K
мы собираемся написать тестовую программу, которая смотрит на кучуK
значений, чтобы мы могли видеть, как она себя ведет. Должно быть очевидно, как выбратьK
, если «правильный ответ» пригоден для использования. Нет?В качестве «правильного ответа» мы будем использовать детали:
Давайте просто попробуем все значения K:
Ах, так что K должно быть 1e16 или больше, если я хочу, чтобы 1e-13 было «ноль».
Итак, я бы сказал, у вас есть два варианта:
K
.источник
K
который трудно и не интуитивно выбрать.Правильный вопрос: как сравнить очки в Cocoa Touch?
Правильный ответ: CGPointEqualToPoint ().
Другой вопрос: одинаковы ли два вычисленных значения?
Ответ выложен здесь: их нет.
Как проверить, если они близко? Если вы хотите проверить, близки ли они, не используйте CGPointEqualToPoint (). Но не проверяйте, если они близко. Сделайте что-то, что имеет смысл в реальном мире, например, проверьте, находится ли точка за линией или находится ли она внутри сферы.
источник
В прошлый раз, когда я проверял стандарт C, не было требования, чтобы операции с плавающей запятой на двойных числах (всего 64 бита, 53-битной мантиссе) были точнее, чем эта точность. Однако некоторые аппаратные средства могут выполнять операции в регистрах с большей точностью, и это требование было истолковано как означающее отсутствие требования очищать биты младших разрядов (помимо точности чисел, загружаемых в регистры). Таким образом, вы можете получить неожиданные результаты сравнений, подобных этому, в зависимости от того, что осталось в регистрах от того, кто спал там последним.
Тем не менее, несмотря на мои попытки удалить его всякий раз, когда я его вижу, в оборудовании, где я работаю, есть много кода на C, который скомпилирован с использованием gcc и запущен на Linux, и мы не заметили ни одного из этих неожиданных результатов за очень долгое время. , Я понятия не имею, происходит ли это потому, что gcc очищает младшие биты для нас, 80-битные регистры не используются для этих операций на современных компьютерах, стандарт был изменен, или как. Я хотел бы знать, может ли кто-нибудь процитировать главу и стих.
источник
Вы можете использовать такой код для сравнения с плавающей точкой с нуля:
Это будет с точностью до 0,1, что достаточно для CGFloat в этом случае.
источник
int
без страховкиtheView.frame.origin.x
находится в / около того диапазонаint
приводит к неопределенному поведению (UB) - или в этом случае 1/100 диапазонаint
.}
источник
Я использую следующую функцию сравнения, чтобы сравнить количество десятичных разрядов:
источник
Я бы сказал, что правильно объявить каждое число как объект, а затем определить три вещи в этом объекте: 1) оператор равенства. 2) метод setAcceptableDifference. 3) само значение. Оператор равенства возвращает true, если абсолютная разница двух значений меньше значения, установленного как допустимое.
Вы можете создать подкласс для объекта в соответствии с проблемой. Например, круглые металлические стержни от 1 до 2 дюймов могут считаться равными по диаметру, если их диаметры различаются менее чем на 0,0001 дюйма. Таким образом, вы должны вызвать setAcceptableDifference с параметром 0.0001, а затем с уверенностью использовать оператор равенства.
источник