Возможно ли прямое сравнение между int и float в C? - PullRequest
8 голосов
/ 21 июля 2009

Я использую Visual Studio 6 со старым временным кодом, написанным на c. Я обнаружил проблему, когда код выглядит следующим образом ..

int x = 3;
float y = 3.0;

if(x == y){
   do some crazy stuff
}

это правильное сравнение? возможно ли во время выполнения выделение для числа с плавающей запятой 3.0000001 и это не получится?

Ответы [ 11 ]

14 голосов
/ 21 июля 2009

Это обычно (т.е. всегда) плохая идея. Как вы и подозревали, сравнение с 3 по 3.0000001 действительно провалится.

Что делает большинство людей, если сравнение с плавающей запятой действительно необходимо, так это выбирает некоторый порог терпимости и соглашается с этим, например:

int x = 3;
float y = 3.0;

// some code here

float difference = (float) x - y;
float tolerableDifference = 0.001;

if ((-tolerableDifference <= difference) && (difference <= tolerableDifference)) {
    // more code
}
6 голосов
/ 21 июля 2009

Я собираюсь немного ослабить тренд здесь. Что касается первого вопроса о том, является ли сравнение действительным, ответ - да. Это совершенно верно. Если вы хотите узнать, точно ли значение с плавающей запятой равно 3, тогда сравнение с целым числом подойдет. Целое число неявно преобразуется в значение с плавающей запятой для сравнения. Фактически, следующий код (по крайней мере, с используемым мной компилятором) выдает идентичные инструкции по сборке.

if ( 3 == f )
    printf( "equal\n" );

и

if ( 3.0 == f )
    printf( "equal\n" );

Так что это зависит от логики и от того, какова намеченная цель. В синтаксисе нет ничего неправильного.

5 голосов
/ 22 июля 2009

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

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

Для этого обсуждения все пункты, приведенные в других ответах, являются действительными. Арифметика с плавающей точкой является неточной, и, следовательно, сравнение для точного равенства, как правило, плохая идея. Значит, Эпсилон - твой друг.

Единственным исключением из правила точного сравнения является тест на ровно ноль. Вполне законно и часто целесообразно проверять на ноль перед делением или логарифмом, поскольку ответ хорошо определен для любого ненулевого значения. Конечно, при наличии правил IEEE и NaN вы можете позволить этому слайду и позже проверить на наличие NaN или Inf.

2 голосов
/ 21 июля 2009

В вашем конкретном примере будет выполнено «делать какие-то безумные вещи». 3.0 не будет 3.0000001 во время выполнения.

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

2 голосов
/ 21 июля 2009

Нет, в вашем случае использования нет проблем, потому что целые числа отображаются точно на числа с плавающей запятой (нет проблемы с десятичным усечением, как, например, с 0,3; но 3 - 1.1E10 в двоичной научной нотации).

В худшем случае, о котором я могу подумать, могут быть целые числа, которые не могут быть представлены в float, потому что между двумя последовательными числами с плавающей запятой существуют «промежутки» больше 1, но даже в том случае, когда целое число приводится чтобы выполнить сравнение с плавающей точкой, оно будет усечено до ближайшего с плавающей точкой, так же как и с литералом с плавающей точкой.

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

2 голосов
/ 21 июля 2009

Это страшно. (Интересно, что еще вы найдете.)

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

Я мог бы предложить что-то вроде этого (проверка на абсолютную ошибку / разницу):

#define EPSILON 0.0001 
if (fabs((float)x - y) < EPSILON) { /* Do stuff. */ }

, который является распространенным подходом и может быть достаточным для ваших целей, если ваши значения x и y "хорошие". Если вы действительно хотите углубиться в тему сравнения чисел с плавающей точкой, эта статья , вероятно, содержит больше информации, чем вы хотите. Это говорит о методе эпсилон:

Если диапазон ожидаемого результата равен известный тогда проверка на абсолютную ошибку это просто и эффективно. Просто сделай убедитесь, что ваше абсолютное значение ошибки больше минимально представимого разница для диапазона и типа float, с которым вы имеете дело.

1 голос
/ 22 июля 2009

Вас может заинтересовать лекция Конференции разработчиков игр Числовая устойчивость для геометрических расчетов (иначе EPSILON НЕ 0,00001!) . В нем подробно описывается выбор хороших пороговых / эпсилон-значений для различных задач.

(+ 1 к упоминанию «Что должен знать каждый учёный-компьютерщик о числах с плавающей запятой» в другом ответе.)

1 голос
/ 21 июля 2009

Ну, я думаю, вы не будете слишком удивлены, услышав, что сравнение поплавков на равенство - это ошибка новичка.

Проблема в том, что многие приращения, меньшие целочисленных значений, не могут быть точно представлены в IEEE с плавающей запятой. Поэтому, если вы попадаете в число с плавающей точкой, пытаясь «проиндексировать» его до значения 3,0 (скажем, с шагом 0,1), вполне возможно, что ваше сравнение на равенство может никогда не быть истинным.

Это также плохая идея с точки зрения силы типов. Вы должны либо преобразовать число с плавающей точкой в ​​целое число, проверить, что ваше int достаточно близко (например, <3,1 и> 2,9 или что-то подобное), или еще лучше, если вы пытаетесь заставить этот float выполнять двойную функцию для чего-то вроде счетчика. , избегай всей идеи.

1 голос
/ 21 июля 2009

Если код выглядит буквально как то, что вы опубликовали (без промежуточных вычислений), то возникает вопрос, совпадают ли 3.0 и (float)3 (поскольку целое число автоматически преобразуется в число с плавающей точкой). Я думаю, что они гарантированно будут одинаковыми в этом случае, потому что 3 точно представлен как float.

В сторону: И даже если целое число не совсем точно представлено как число с плавающей точкой (т.е. если оно действительно большое), я бы предположил, что в большинстве реализаций x.0 и (float)x будут одинаковыми, потому что компилятор сгенерирует x.0 в первую очередь, если не делать что-то похожее на (float)x? Тем не менее, я думаю, что это не гарантируется стандартом.

1 голос
/ 21 июля 2009

Суть проблемы в том, что числа с плавающей запятой, которые имеют конечное представление в десятичной системе счисления, не всегда имеют конечное представление в двоичной системе счисления 2.

...