Странная проблема сравнения поплавков в объективе-C

В какой-то момент в алгоритме мне нужно сравнить значение float свойства classа с поплавком. Поэтому я делаю это:

if (self.scroller.currentValue <= 0.1) { } 

где currentValue – свойство float.

Однако, когда у меня есть равенство и self.scroller.currentValue = 0.1 оператор if не выполняется и код не выполняется! Я узнал, что могу исправить это, отбросив 0,1 на флоат. Как это:

 if (self.scroller.currentValue <= (float)0.1) { } 

Это прекрасно работает.

Может ли кто-нибудь объяснить мне, почему это происходит? Определяется ли значение 0.1 по умолчанию двойным или что-то еще?

Благодарю.

Я считаю, что не нашел стандарта, который так говорит, что при сравнении float с double float бросается в double перед сравнением. Числа с плавающей точкой без модификатора считаются double в C.

Однако в C нет точного представления 0,1 в поплавках и двойниках. Теперь, используя float, вы получаете небольшую ошибку. Использование double дает вам еще меньшую ошибку. Теперь проблема заключается в том, что, бросая float в double вы переносите большую ошибку float . Конечно, теперь они не сравниваются.

Вместо использования (float)0.1 вы можете использовать 0.1f что немного лучше читать.

Проблема заключается в том, что, как вы сказали в своем вопросе, вы сравниваете поплавок с двойным.

Существует более общая проблема с сопоставлением поплавков, это происходит потому, что, когда вы выполняете расчет по числу с плавающей запятой, результат вычисления может быть не таким, каким вы ожидаете. Весьма распространено, что последний бит результирующего float будет неправильным (хотя погрешность может быть больше, чем только последний бит). Если вы используете == для сравнения двух поплавков, то все биты должны быть одинаковыми для равных поплавков. Если ваш расчет дает немного неточный результат, тогда они не будут сравниваться с равными, если вы их ожидаете. Вместо сравнения таких значений вы можете сравнить их, чтобы убедиться, что они почти равны. Для этого вы можете принять положительную разницу между поплавками и посмотреть, меньше ли это заданного значения (называемого epsilon).

Чтобы выбрать хороший эпсилон, вам нужно немного понять о числах с плавающей запятой. Числа с плавающей запятой работают аналогично представлению числа с заданным числом значимых цифр. Если мы работаем до 5 значащих цифр, и ваш результат вычислений приводит к тому, что последняя цифра результата будет неправильной, то у 1.2345 будет ошибка + -0.0001, тогда как у 1234500 будет ошибка +100. Если вы всегда основываете свою погрешность на значении 1.2345, то ваша процедура сравнения будет идентична == для всех значений, отличных от 10 (при использовании десятичной). Это хуже в двоичном, все значения больше 2. Это означает, что выбранный epsilon должен быть относительно размера поплавков, которые мы сравниваем.

FLT_EPSILON – это промежуток между 1 и следующим ближайшим поплавком. Это означает, что может быть хорошим epsilon выбрать, если ваш номер находится между 1 и 2, но если ваше значение больше 2, используя этот epsilon, это бессмысленно, потому что разрыв между 2 и ближайшим ближайшим поплавком больше, чем epsilon. Таким образом, мы должны выбрать epsilon относительно размера наших поплавков (так как ошибка в вычислении относится к размеру наших поплавков).

Хорошая (ish) процедура сравнения с плавающей запятой выглядит примерно так:

 bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier) { float epsilon; /* May as well do the easy check first. */ if (a == b) return true; if (a > b) { epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier; } else { epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier; } return fabs (a - b) <= epsilon; } 

Эта процедура сравнения сравнивает поплавки относительно размера самого большого поплавка, переданного в. scalbnf(1.0f, ilogb(a)) * FLT_EPSILON находит разрыв между a и ближайшим ближайшим плавающим. Затем это умножается на epsilonMultiplier , поэтому размер разницы может быть скорректирован в зависимости от того, насколько неточным будет результат вычисления.

Вы можете сделать простую процедуру compareLessThan следующим образом:

 bool compareLessThan (float a, float b, unsigned epsilonMultiplier) { if (compareNearlyEqual (a, b, epsilonMultiplier) return false; return a < b; } 

Вы также можете написать очень похожую функцию compareGreaterThan .

Стоит отметить, что сравнение таких поплавков может быть не всегда таким, каким вы хотите. Например, это никогда не обнаружит, что float близок к 0, если он не равен 0. Чтобы исправить это, вам нужно решить, какое значение вы считали близким к нулю, и написать дополнительный тест для этого.

Иногда неточности, которые вы получаете, не зависят от размера результата вычисления, но будут зависеть от значений, которые вы ввели в расчет. Например, sin(1.0f + (float)(200 * M_PI)) даст гораздо менее точный результат, чем sin(1.0f) (результаты должны быть идентичными). В этом случае ваша процедура сравнения должна будет посмотреть на число, которое вы указали в вычислении, чтобы узнать пределы погрешности ответа.

У двойников и поплавков есть разные значения для магазина мантиссы в двоичном формате (float – 23 бит, double 54). Они почти никогда не будут равны.

Статья IEEE Float Point о википедии может помочь вам понять это различие.

В C литерал с плавающей запятой, например, 0,1, является двойным, а не плавающим. Поскольку типы сравниваемых элементов данных различны, сравнение выполняется с более точным типом (double). Во всех реализациях, о которых я знаю, float имеет более короткое представление, чем double (обычно выраженное как 6 против 14 десятичных знаков). Более того, арифметика находится в двоичном выражении, а 1/10 не имеет точного представления в двоичном формате.

Таким образом, вы принимаете float 0.1, который теряет точность, расширяя его до двух, и ожидая, что он сравним, равный удвоенной 0,1, которая теряет меньше точности.

Предположим, что мы делали это в десятичной форме, с плавающей точкой, состоящей из трех цифр и двойным – шесть, и мы сравнивали ее с 1/3.

Мы сохранили значение float равным 0.333. Мы сравниваем его с двойным со значением 0.333333. Мы конвертируем float 0.333 в double 0.333000 и находим его другим.

0.1 на самом деле является очень плохим значением для хранения двоичных файлов. На основании 2 1/10 представляет собой бесконечно повторяющуюся фракцию

 0.0001100110011001100110011001100110011001100110011... 

Как указывали некоторые, сравнение должно проводиться с постоянной с той же точностью.

Как правило, на любом языке вы не можете рассчитывать на равенство типа float-типа. В вашем случае, так как похоже, что у вас больше контроля, похоже, что по умолчанию значение по умолчанию не равно. Вероятно, вы можете найти это с помощью sizeof (0.1) (против sizeof (self.scroller.currentValue).

Преобразуйте его в строку, а затем сравните:

 NSString* numberA = [NSString stringWithFormat:@"%.6f", a]; NSString* numberB = [NSString stringWithFormat:@"%.6f", b]; return [numberA isEqualToString: numberB]; 
  • Составляют ли какие-либо JIT-компиляторы JVM код, который использует векторизованные инструкции с плавающей запятой?
  • Разница в арифметике с плавающей запятой между x86 и x64
  • Алгоритм преобразования двоичного кода IEEE 754 в строку?
  • Каково первое целое число, которое плавающий IEEE 754 не может точно представлять?
  • почему f помещается после значений float?
  • Проблемы с двойной суммой Java
  • Что случилось с этим делением?
  • Когда следует использовать ключевое слово "strictfp" в java?
  • Как проверить, что строка обрабатывается двойным?
  • Почему 24.0000 не равно 24.0000 в MATLAB?
  • C: Как поместить float на интервал [-pi, pi)
  • Interesting Posts

    конвертировать streamовые буферы в utf8-строку

    Существуют ли какие-либо различия между окончательными и проектными спецификациями 802.11n?

    Проверить строку для палиндрома

    Перенаправление в другой домен, но сохранить введенный домен

    Currying 3 Аргументы в Haskell

    Добавьте элемент в меню «отправить» всем пользователям (winxp)

    Загрузите и установите версию Xcode 10 без учетной записи разработчика Premium

    Возможно ли создать программный RAID 1 с разными размерами сектора?

    Как понизить Chrome от dev до бета-канала, не теряя моего профиля?

    Вывод в формате таблицы в Java System.out

    Почему cudaMalloc () использует указатель на указатель?

    Как изменить тип данных для столбца в MySQL?

    Преобразование многих форматов даты-времени в один стандартный формат

    Как открыть всплывающее окно WPF при нажатии другого элемента управления, используя только разметку XAML?

    MATLAB – извлечение строк матрицы

    Давайте будем гением компьютера.