Проблема в том, что, как вы предположили в своем вопросе, вы сравниваете float с двойным.
Существует более общая проблема со сравнением чисел с плавающей запятой, потому что, когда вы выполняете вычисление числа с плавающей запятой, результат вычисления может быть не совсем таким, как вы ожидаете. Довольно часто последний бит результирующего числа с плавающей запятой будет неправильным (хотя погрешность может быть больше, чем только последний бит). Если вы используете ==
для сравнения двух чисел с плавающей запятой, тогда все биты должны быть одинаковыми, чтобы числа с плавающей запятой были равны. Если ваш расчет дает немного неточный результат, тогда они не будут сравниваться с равными, когда вы этого ожидаете. Вместо того, чтобы сравнивать такие значения, вы можете сравнить их, чтобы увидеть, почти равны ли они. Для этого вы можете взять положительную разницу между числами с плавающей запятой и посмотреть, меньше ли она заданного значения (называемого эпсилоном).
Чтобы выбрать хороший эпсилон, вам нужно немного разбираться в числах с плавающей запятой. Числа с плавающей запятой работают аналогично представлению числа заданным числом значащих цифр. Если мы работаем с 5 значащими цифрами, и в результате вашего расчета последняя цифра результата неверна, то 1,2345 будет иметь ошибку + -0,0001, тогда как 1234500 будет иметь ошибку + -100. Если вы всегда основываете свой предел погрешности на значении 1,2345, тогда ваша процедура сравнения будет идентична ==
для всех значений больше 10 (при использовании десятичной дроби). В двоичном формате это хуже, все значения больше 2. Это означает, что выбранный нами эпсилон должен быть относительно размера сравниваемых чисел с плавающей запятой.
FLT_EPSILON - это промежуток между 1 и ближайшим к нему поплавком. Это означает, что может быть хорошим эпсилоном выбрать, если ваше число находится между 1 и 2, но если ваше значение больше 2, использование этого эпсилона бессмысленно, потому что разрыв между 2 и следующим ближайшим поплавком больше, чем эпсилон. Итак, мы должны выбрать эпсилон относительно размера наших поплавков (поскольку ошибка в вычислении связана с размером наших поплавков).
Хорошая подпрограмма сравнения с плавающей запятой выглядит примерно так:
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)
(результаты должны быть идентичными). В этом случае ваша процедура сравнения должна будет смотреть на число, которое вы вводите в расчет, чтобы узнать погрешность ответа.
person
James Snook
schedule
17.03.2016