Рацеев С.М. Программирование на языке Си
.pdf00011011 << 3 = 11011000 |
(27 |
<< 3 = 216), |
|
00011011 << 4 = 10110000 |
(27 |
<< 4 = 176), |
|
00011011 << 5 = 01100000 |
(27 |
<< 5 |
= 96), |
00011011 >> 1 = 00001101 |
(27 |
>> 1 |
= 13), |
00011011 >> 2 = 00000110 |
(27 |
>> 2 |
= 6). |
Если операция сдвига вправо (>>) применяется к переменным знакового типа (signed), то освободившиеся разряды заполняются единицами. Например, для 8-разрядного знакового числа (char) -27
имеют место быть такие равенства: |
|
|
|
11100101 << 1 = 11001010 |
(-27 << 1 |
= -54), |
|
11100101 << 2 = 10010100 |
(-27 |
<< 2 |
= -108), |
11100101 << 3 = 00101000 |
(-27 |
<< 3 |
= 40), |
11100101 >> 1 = 11110010 |
(-27 |
>> 1 |
= -14), |
11100101 >> 2 = 11111001 |
(-27 |
>> 2 |
= -7). |
Заметим, что сдвиг влево (вправо) соответствует умножению (делению) левого операнда на степень числа 2, равную значению второго операнда.
Операция ~ является одноместной, она изменяет каждый бит целого числа на обратный. Операции &, | и ^ – двуместные; операнды этих операций – целые величины одинаковой длины. Операции выполняются попарно над всеми двоичными разрядами операндов.
Определение битовых операций:
Бит левого |
Бит правого |
~x |
x & y |
x | y |
x ^ y |
операнда (x) |
операнда (y) |
|
|
|
|
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
0 |
Например:
Операция ~: |
Операция &: |
Операция |: |
Операция ^: |
~01010101 |
01010101 & |
01010101 | |
01010101 ^ |
|
00000001 |
00101011 |
01011010 |
10101010 |
00000001 |
01111111 |
00001111 |
201
Приведем также некоторые формулы с использованием операций &, | и ^, которые нам понадобятся в дальнейшем:
x | 1 = 1, x | 0 = x,
x & 1 = x, x & 0 = 0, x ^ 1 = ~x, x ^ 0 = x.
12.2. Примеры с использованием битовых операций
Пример 1. Написать функцию, которая печатает двоичное представление целого числа a.
Функция DisplayBits получает в качестве параметра число a, которое требуется представить в двоичном виде, и количество занимаемых данным числом бит равно n.
#include <stdio.h>
void DisplayBits(int a, int n)
{
int i, bit;
for (i = n - 1; i >= 0; i--)
{
/* выделяем i-й справа бит */ bit = (a >> i) & 1;
printf("%d ", bit);
}
}
int main( )
{
int a; scanf("%d", &a);
DisplayBits(a, sizeof(int)*8); return 0;
}
202
Чтобы вывести двоичное представление неотрицательного целого числа, можно использовать рекурсию:
#include <stdio.h>
void Print(unsigned int a)
{
if (a)
{
Print(a >> 1); printf("%u ", a&1);
}
}
int main( )
{
unsigned int a; scanf("%u", &a); Print(a);
return 0;
}
Пример 2. Пусть имеется целое число a. Требуется i-й справа бит в числе a установить в значение 1, не меняя значения других битов.
Обозначим через b целое число, содержащее 1 в i-м бите и 0 – в остальных. Его можно получить так: b = 1 << i. Тогда требуемое число получается таким образом: a | b.
a |= 1 << i;
Пример 3. Обобщим предыдущую задачу. Пусть имеется целое число a. Требуется k подряд идущих бит, начиная с i-й справа позиции, установить в значение 1, не меняя значения других битов.
Рассмотрим число b=(0…01…1)2, заданное в двоичном виде, в котором сначала следуют только нулевые биты, а затем следуют
ровно k единичных бит. Учитывая формулу
1 + 2 + 22 + 2k-1 = 2k – 1,
203
которая верна для любого натурального k, число b равно значению 2k – 1, что на языке битовых операций означает такое представление:
b = (1 << k) – 1.
Для решения данной задачи достаточно сдвинуть все биты в числе b на i позиций влево и применить операцию побитового ИЛИ к полученному числу и числу a:
a | ((1 << k – 1) << i).
В итоге, получаем такое выражение: a |= (1 << k – 1) << i.
Пример 4. Пусть имеется целое число a. Требуется i-й справа бит в числе a установить в значение 0, не меняя значения других битов.
Рассмотрим целое число b, содержащее 0 в i-м бите и 1 – в остальных: b = ~(1 << i). Тогда требуемое число получается так: a & b.
a &= ~(1 << i);
Пример 5. В целом числе a требуется инвертировать i-й справа бит, не меняя значения других битов.
a ^= (1 << i);
Пример 6. Даны две переменные x и y целого типа. Требуется i-й справа бит переменной x поставить на j-ю справа позицию переменной y, не изменяя при этом остальные биты в y.
Сначала обнулим все биты в переменной x, кроме i-го справа бита xi, который поставим на j-ю справа позицию в x:
((x >> i) & 1) << j.
Затем обнулим j-й справа бит в переменной y, не изменяя значения остальных битов:
y & ~(1 << j).
Для решения задачи осталось применить побитовую операцию ИЛИ к предыдущим двум выражениям:
y = ((x >> i) & 1) << j | y & ~(1 << j).
204
Пример 7. Найти количество единичных битов в целом неотрицательном числе a.
Функция BitCount каждый раз проверяет состояние младшего бита, постепенно сдвигая все биты на одну позицию вправо, и так до тех пор, пока не будут проверены все n битов целого числа.
short BitCount(unsigned int a, short n)
{
short count; int i;
count = 0;
for (i = 0; i < n; i++)
{
if (a & 1) count++;
a >>= 1;
}
return count;
}
Функцию BitCount можно немного оптимизировать, учитывая то обстоятельство, что если после очередного сдвига всех битов на одну позицию вправо число a становится нулевым, то в нем нет более единичных битов. Поэтому нет необходимости всегда просматривать все n битов, а лишь до тех пор, пока не будет учтен самый старший (левый) единичный бит.
short BitCount2(unsigned int a)
{
short count; count = 0; while (a != 0)
{
if (a & 1) count++;
a >>= 1;
205
}
return count;
}
Последнюю функцию можно компактно записать с помощью рекурсивной функции:
int Count (unsigned int a)
{
return a ? (a & 1) + Count(a >> 1) : 0;
}
Пример 8. Дано целое число n > 0, являющееся некоторой степенью числа 2: n = 2k. Найти целое число k – показатель этой степени.
int Degree(unsigned int n)
{
int k; k = -1;
while (n)
{
n >>= 1; k++;
}
return k;
}
Пример 9. Пусть имеется некоторое натуральное число n. Проверить, является ли оно степенью числа 2, то есть найдется ли такое целое неотрицательное число k, что n = 2k.
int Deg_of_2(unsigned int n)
{
return (n & (n-1)) == 0;
}
206
Пример 10 (алгоритм быстрого возведения в степень).
Пусть требуется вычислить an, где a и n – некоторые целые числа, причем n ≥ 0. Рассмотрим такие степени числа a:
a, a2 , a4 , a8 , ..., a2t ,
где t = [log2 n]. Заметим, что каждое число из указанной последовательности получается путем умножения предыдущего числа самого на себя. Представим число n в виде такого разложения:
n nt 2t nt 1 2t 1 ... n1 2 n0 ,
где n0, n1, …, nt – числа из множества {0, 1}. Тогда число an может быть вычислено таким образом:
an an0 an1 2an2 4... ant 1 2t 1 ant 2t .
Поэтому количество операций умножения при вычислении an по данному методу не превосходит 2 log2 n.
long Degree(long a, unsigned long n)
{ |
|
long deg, |
/* степени числа a: a, a2, a4, a8,… */ |
rez; |
/* результат возведения в степень */ |
rez = 1; |
|
deg = a; |
|
while (n != 0)
{
if (n & 1) rez *= deg;
deg *= deg; n >>= 1;
}
return rez;
}
Если записать данную функцию с помощью рекурсии, то получится такая компактная функция:
long Deg(long a, unsigned long n)
{
return n ? ((n & 1) ? a*Deg(a*a, n >> 1) : Deg(a*a, n >> 1)) : 1;
207
}
Пример 11. Требуется в целочисленной переменной a, занимающей не менее два байта памяти, поменять местами последний (младший) и предпоследний байты.
a = (a & ~0xFFFF) | ((a & 0xFF) << 8) | ((a >> 8) & 0xFF);
Пример 12. Пусть имеется массив целых чисел a размера n, состоящий из попарно различных элементов. Вывести на экран всевозможные подмножества элементов, входящие в состав массива a.
Для написания алгоритма решения данной задачи заметим следующее. Пусть A = {a1, …, an} – некоторое множество. Тогда любому подмножеству B множества A взаимно однозначно соответствует двоичный вектор vB = (v1, …, vn), в котором vi =1 тогда и только тогда, когда ai принадлежит множеству B. Всего таких различных двоичных векторов длины n ровно 2n. Все данные двоичные векторы длины n суть двоичная запись чисел от 0 до 2n-1. Поэтому алгоритм получается следующим.
#include<stdio.h>
void Print(int *a, int size, int v)
{
int i;
for (i = 0; i < size; i++)
{
if (v & 1) printf("%d ", a[i]);
v >>= 1;
}
printf("\n");
}
void Subsets(int *a, int size)
{
208
int i, n;
n = 1 << size;
for(i = 1; i < n; i++) Print(a, size, i);
}
int main()
{
int a[5] = {1, 2, 3, 4, 5}; Subsets(a, 5);
return 0;
}
Пример 13. Заметим, что операцию ИСКЛЮЧАЮЩЕЕ ИЛИ
(^) можно рассматривать как сложение по модулю 2 в кольце вычетов Z2. Поэтому верно тождество x^x=0 для любого x из Z2. Поскольку операция ^ является побитовой, то тождество x^x=0 верно также для любой целочисленной переменной x. Исходя из данного факта, построим алгоритм, который меняет местами значения двух целочисленных переменных без использования буферной переменной.
int x = 1, y = 2;
x ^= y; |
/* x = x^y */ |
y ^= x; |
/* y = x^y = (x^y)^y =x */ |
x ^= y; |
/* x = x^y = (x^y)^x =y */ |
/* теперь x=2, y=1 */
При этом все операции можно записать в одну строчку:
x ^= y ^= x ^= y;
12.3.Задачи
1.Найти количество нулевых и единичных битов в целом неотрицательном числе a.
209
2.Пусть имеется массив a размером n ≤ 32, состоящий из нулей и единиц. Требуется записать значения элементов массива a в переменную типа unsigned long в виде последовательности би-
тов a[n-1] … a[1]a[0].
3.Написать функцию проверки на четность целых чисел, используя только битовые операции.
4.Пусть дано целое число a. Вывести вначале значения его битов с четными индексами, а затем – с нечетными.
5.Пусть дано целое число a. Проверить, чередуются ли в нем единичные и нулевые биты.
6.В целом числе a сдвинуть все биты влево на k позиций, заменив освободившиеся биты единицами.
7.Осуществить циклический сдвиг битов в целом числе a на k позиций влево.
8.Осуществить циклический сдвиг битов в целом числе a на k позиций вправо.
9.Найти номер первого (последнего) справа единичного бита в целом числе a.
10.Найти номер первого (последнего) справа нулевого бита в целом числе a.
11.Определить, расположены ли биты в целом числе a в порядке возрастания, то есть представимо ли данное число в виде 2n – 1 для некоторого n.
12.Переставить биты в целом числе в обратном порядке.
13.Пусть имеется целое число a > 0. Записать в строковую переменную восьмеричное представление данного числа, используя следующее правило: двоичное представление числа a разбивается справа налево на триады (тройки цифр), и каждая триада заменяется восьмеричной цифрой.
14.Пусть имеется целое число a > 0. Записать в строковую переменную шестнадцатеричное представление данного числа, используя правило из предыдущей задачи, разбивая двоичное представление числа на тетрады (четверки цифр).
15.Написать функцию
unsigned long Perest(unsigned long x, unsigned char *pi, char n),
которая переставляет биты в числе x в соответствии с переста-
210