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

Для чего это необходимо, в библиотеке Universal Language Selector Wikimedia есть крючки для этого: https://www.mediawiki.org/wiki/Extension:UniversalLanguageSelector

См. function getFrequentLanguageList в ресурсах / js / ext.uls.init.js. Прямая ссылка: https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/extensions/UniversalLanguageSelector.git;a=blob;f=resources/js/ext.uls.init.js;hb= HEAD

Все еще зависит от сервера или, более конкретно, от MediaWiki API. Причина, по которой я показываю это, - это хороший пример получения всей полезной информации о языке пользователя: язык браузера, Accept-Language, геолокация (с получением информации о стране / языке из CLDR) и, конечно же, пользовательские настройки сайта.

23
задан smorgan 26 October 2009 в 14:07
поделиться

7 ответов

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

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

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

30
ответ дан Georg Schölly 26 October 2009 в 14:07
поделиться

Двойные числа и числа с плавающей запятой имеют разные значения для хранилища мантиссы в двоичном формате (число с плавающей запятой 23 бита, двойное число 54). Они почти никогда не будут равны.

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

4
ответ дан MarkPowell 26 October 2009 в 14:07
поделиться

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

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

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

У нас есть сохраненное значение с плавающей запятой, равное 0,333. Мы сравниваем его с двойным значением 0,333333. Мы конвертируем число с плавающей запятой в 0,333 в удвоенное значение 0,333000 и находим его другим.

4
ответ дан David Thornley 26 October 2009 в 14:07
поделиться

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

0.0001100110011001100110011001100110011001100110011...

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

4
ответ дан epatel 26 October 2009 в 14:07
поделиться

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

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

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

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

Хорошая (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.

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

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

6
ответ дан James Snook 26 October 2009 в 14:07
поделиться

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

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

return [numberA isEqualToString: numberB];
-1
ответ дан Henry Sou 26 October 2009 в 14:07
поделиться

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

1
ответ дан Lou Franco 26 October 2009 в 14:07
поделиться