Подтвердить что ты не робот

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

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

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 по умолчанию двойным или что-то?

Спасибо.

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Существует более общая проблема при сравнении поплавков, это происходит потому, что, когда вы выполняете расчет по номеру с плавающей запятой, результат вычисления может быть не таким, каким вы ожидаете. Весьма распространено, что последний бит результирующего 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) (результаты должны быть идентичными). В этом случае ваша процедура сравнения должна будет посмотреть на число, которое вы указали в вычислении, чтобы узнать пределы погрешности ответа.

Ответ 3

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

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

Ответ 4

В 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 и находим его разными.

Ответ 5

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

0.0001100110011001100110011001100110011001100110011...

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

Ответ 6

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

Ответ 7

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

NSString* numberA = [NSString stringWithFormat:@"%.6f", a];
NSString* numberB = [NSString stringWithFormat:@"%.6f", b];

return [numberA isEqualToString: numberB];