Странная проблема сравнения поплавков в объективе-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]; 
  • Почему изменение 0.1f to 0 замедляет производительность на 10x?
  • Почему преобразование из float в double изменяет значение?
  • Быстрый метод для округления двойного до 32-битного int
  • C ++ десятичные типы данных
  • Почему Java Double.compare (double, double) реализован так, как есть?
  • Детали реализации оборудования с плавающей запятой
  • Форматирование удваивается для вывода в C #
  • Меню поддержки поддержки Android Design Library (FAB)
  • Спецификатор ширины печати для поддержания точности значения с плавающей запятой
  • В MATLAB переменные ДЕЙСТВИТЕЛЬНО двойной точности по умолчанию?
  • Сделать поплавок показывать только два десятичных знака
  • Interesting Posts

    В чем разница между анонимными методами (C # 2.0) и lambda-выражениями (C # 3.0)?

    Как нарисовать стрелу в Matlab?

    Должна ли таблица базы данных иметь первичные ключи?

    Получение ошибки: не удалось найти class ‘android.app.AppOpsManager’, на который ссылается метод com.google.android.gms.common.GooglePlayServicesUtil.zza

    Как я могу запросить нулевые значения в структуре сущностей?

    Можно ли удалить папку SkyDrive с моего компьютера, но не из облачного хранилища SkyDrive?

    Как запустить 2 SSD-накопителя в RAID-0?

    Разница между сокращением и foldLeft / fold в функциональном программировании (в частности, Scala и Scala API)?

    Получение * buntu 17.04 (или других) для соблюдения моих предпочтений при установке на внешний диск UEFI / secureboot system

    Аварии Windows 7 BSOD

    Возможно ли запустить приложение .NET 4.5 на XP?

    Преобразование строк в дату mm / dd / yy до YYYY-MM-DD в java

    Как сбрасывать последовательность первичных ключей postgres, когда она выпадает из синхронизации?

    Как запустить другое задание из hudson в качестве этапа предварительной сборки?

    Установка gtk + тем в Windows

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