Приведение числовых типов данных в C - PullRequest
2 голосов
/ 13 января 2010

Я видел этот вопрос на SO о кастинге, и в ответе конкретно упоминались числовые типы. Я думал, что понял это до сих пор, когда я должен это сделать.

У меня есть два int, которые я хочу добавить, разделить на два и сохранить в другое int. Поэтому я создаю временную переменную с именем 'bucket', которая на один тип данных больше, добавляю два целых числа и сдвигаюсь вправо на единицу.

Я придумал несколько способов сделать это, все, кажется, работают, но некоторые, я думаю, не нужны. Пожалуйста, просмотрите это и дайте мне знать, где я неправильно понял K & R.

Вариант 1:

bucket      = ( (long) ADCBUF0 + (long) ADCBUF7) >> 1;
rem_volt       = bucket;

Случай 2:

bucket      = ( (long) ADCBUF1 + (long) ADCBUF8) >> 1;
loc_volt       = (unsigned int) bucket;

Дело 3:

bucket      = (long) ADCBUF2 + (long) ADCBUF9;
current     = (unsigned int) (bucket >> 1);

Дело 4:

bucket      = ADCBUF3 + ADCBUFA;
dac_A         = (unsigned int) (bucket >> 1);   

Дело 5:

bucket      = (long) (ADCBUF4 + ADCBUFB) >> 1;
dac_B       = (unsigned int) bucket;

Я думаю, что Случай 4 или Случай 5 - правильный способ сделать это, так как Случай 1 неявно приводит значение long к целому, Случай 2, я думаю, является правильным, но более типизирующим, Случай 3, я думаю, безуспешно переводит целые числа в long когда нужно разыграть только их сумму.

Спасибо за ваши мысли.

EDIT: Спасибо за комментарии до сих пор! Чтобы уточнить, я неаккуратен в отношении неподписанных и подписанных, все они должны быть без знака. Я пытаюсь избежать переполнения при добавлении. Мой long на 2 байта больше, чем мой int. (Так что это действительно намного больше, чем нужно, я мог бы просто проверить бит переполнения.) Я работаю на встроенной платформе, я никогда не буду портировать это (клянусь!), Но я пытаюсь обдумать, кто придет за мной и попытается разобраться, что я делаю, поэтому я буду использовать деление на 2, а не битовое смещение.

Ответы [ 5 ]

4 голосов
/ 13 января 2010

(Вы сказали, что ваши переменные являются целыми числами, a и b - это то, что я использовал ниже, и я использую это предположение: int a, b;.)

Проблема с получением среднего из двух чисел путем сложения и последующего деления состоит в том, что вы можете переполниться (как упоминал Лоренс Гонсалвес в своем комментарии). Если вы знаете, что сумма не будет превышать INT_MAX, тогда использовать int можно, и больше ничего делать не нужно:

int avg = (a + b) / 2;

Если сумма может переполниться, то переход к типу, который не переполняется, например long, вполне подойдет; однако помните, что long и int могут иметь одинаковый размер (INT_MAX может равняться LONG_MAX), и в этом случае это не поможет, за исключением того, что INT_MAX будет намного больше - не менее 2 147 483 674.

int avg = ((long)a + b) / 2;

Обратите внимание, что при добавлении b к a , b автоматически преобразуется в long. Вам может потребоваться привести окончательный результат к int, чтобы избежать предупреждений (в том числе из программы lint):

int avg = (int)(((long)a + b) / 2);

Однако помните, что целочисленные пределы определяются реализацией; реальная проблема заключается в том, может ли добавление когда-либо превышать INT_MAX, и приведение - это просто способ явно указать тип и, таким образом, избежать непреднамеренного переполнения.


Если вы хотите разделить на два, используйте / 2, не пытайтесь разобраться с битовыми сдвигами. Компиляторы достаточно умны, чтобы оптимизировать подобные вещи годами, особенно когда вы используете константы, а ясность кода важнее.

3 голосов
/ 14 января 2010

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

Можно рассчитать среднее (округленное вниз) двух целых чисел без знакабез переполнения или использования промежуточного более широкого типа с использованием некоторой хитрости:

avg = (a & b) + ((a ^ b) >> 1)

Почему это работает: для каждого столбца двоичного сложения a & b дает бит "1", если биты в этом столбцесумма до 2, а (a ^ b) >> 1 дает бит «1» для столбца справа (который можно представить как 1/2 для столбца, который вы добавляете), где биты в этом столбце составляют 1.

2 голосов
/ 13 января 2010

Вы не предоставляете некоторые важные детали, но при некоторых разумных допущениях, это 4 и 5, которые разбиты, в то время как 1-3 довольно хорошо.

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

В любом случае, минимальный правильный способ выполнения сложения и сдвига будет

bucket = ((long) ADCBUF0 + ADCBUF7) >> 1; 

Переключение на больший тип только одним операндом достаточно. Конечно, вы можете оставить оба броска, если вам нравится более «симметричный» внешний вид.

Или вы можете сделать это вообще без каких-либо приведений (при условии, что bucket объявлен с «большим» типом)

bucket = ADCBUF0;
bucket = (bucket + ADCBUF7) >> 1; 

Что касается окончательного назначения, если получатель сам по себе является unsigned int (это совершенно неясно из вашего вопроса), нет никакой реальной необходимости в каком-либо приведении, если вы не пытаетесь подавить предупреждения компилятора. Я предполагаю, что это unsigned int. В этом случае просто

rem_volt = bucket; 

будет работать. Если это не unsigned int, то явное приведение может иметь значение, но невозможно сказать, пока вы не предоставите эту важную деталь.

Неважно, где вы делаете сдвиг в вашем случае (в первом или во втором утверждении), опять же, если bucket имеет тип "больше". Если bucket объявлен с тем же типом, что и переменные ADC..., то сдвиг должен быть сделан рано (в первом выражении).

Что касается ваших вариантов: 1 в порядке, но второе приведение является технически избыточным (вопрос стиля). 2 в порядке, с двумя чрезмерными забросами. 3 также в порядке, с чрезмерным броском. 4 не работает, поскольку он не защищает от переполнения (если это было вашим намерением). 5 сломан таким же образом.

Другими словами, из всех ваших вариантов номер 1 выглядит лучше (опять же, предполагая, что приведение к unsigned int избыточно).

P.S. Если вы хотите разделить что-то на 2, самый разумный способ сделать это - использовать оператор деления / и константу 2 в качестве делителя. Нет смысла вносить какие-либо изменения в картину.


Кроме того, Мэтью Слэттери в своем ответе предоставил не переполненный способ вычисления среднего значения с использованием операций переворота битов. Это также может быть сделано другим (возможно, более читабельным) способом через

average = ADCBUF0 / 2 + ADCBUF7 / 2 + (ADCBUF0 % 2 & ADCBUF7 % 2);

или, если вы предпочитаете подход с битовой перестановкой

average = (ADCBUF0 >> 1) + (ADCBUF7 >> 1) + (ADCBUF0 & ADCBUF7 & 1);
1 голос
/ 13 января 2010

Я бы использовал 2 или 3. В первой строке вы должны убедиться, что добавление выполняется как длинный, чтобы предотвратить переполнение. Во 2-й строке указание, что вы выполняете downcast, предотвращает предупреждения компилятора.

4 и 5 фактически сломаны на платформе, где long больше, чем int.

0 голосов
/ 14 января 2010

Это было сказано в других ответах, но, возможно, не так явно. Чтобы ответить на ваш вопрос: «где я неправильно понял K & R»:

  • арифметическая операция между 2 различные типы выполняются «продвигая» от низшего к высшему, и результат переносится при этом уровень - так что несколько бросков между только 2 типа избыточны
  • присвоение высшего типа низшему делается путем усечения - приведение не меняет данные (это только для предупреждений компилятора)

Мой вопрос к ОП: как вы думаете, "все работает"? Я не мог найти способ сделать это (со значимыми данными), даже с некоторой интерполяцией ваших предположений и примеров. Вы тестировали с достоверными данными? Судя по именам ваших переменных, вы просто взяли пример кода из приложения. Это не лучшее место, чтобы проверить что-то вроде вопроса. Легко ошибиться, если у вас более 10 переменных. Протестируйте различные опции в специальной тестовой программе с минимальными изменениями.

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

printf ("Размеры: int =% d long =% d \ n", sizeof (int), sizeof (long)); // нота круглые скобки!

для проверки размеров без необходимости знать о limit.h или другом включаемом файле для определения платформы. Включение этого результата в ваше исходное сообщение могло бы устранить некоторые вопросы, потому что на многих современных настольных платформах значение int равно размеру long (используя long long для увеличения размера слова)

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