Файл: А. Б. Шнейвайс основы программирования.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 06.12.2023

Просмотров: 312

Скачиваний: 6

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Как получаются очередные слагаемые?
1. Побитовые операции выполняются над всеми битами ячейки одно- временно. Побитовое умножение 32-битовой маски
0101010101010101010101010101010101010101010101010101010101010101
(или 55555555 в шестнадцатеричной записи) на исходное s получит значение, которое будет иметь единицы в тех нечётных двоичных разрядах, в которых они были в исходном значении s:
s=bc637eff 1 0 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 Нашли все m=55555555 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 единицы из
------------------------------------------------------------------------------ нечётных s&m=14415455 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 1 0 1 0 1 битов.
2. При подвижке исходного значения s вправо на один бит (СИ-шная операция s >> 1) содержимое всех чётных битов сместится в сосед- ние нечётные, так что умножение результата подвижки на прежнюю маску выделит единицы из чётных битов исходного N :
s>>1=5e31bf7f 0 1 0 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 Сдвиг на 1 бит m=55555555 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 Нашли все
------------------------------------------------------------------------------- единицы из s>>1&m=54111555 0 1 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 чётных битов
3. Если теперь в качестве нового текущего значения s взять сумму s = s&0x55555555 + (s >> 1)&0x55555555 ,
то в очередной паре его битов будет находиться двубитовое двоичное число равное количеству единиц,содержащемуся в соответствующих складываемых битах исходного двоичного представления N . Нули в чётных битах маски в обоих случаях побитового умножения обес- печивают в чётных битах результата наличие нуля. Так что появле- ние на его месте единицы в уме (если оно потребуется при двоичном сложении) корректно представит найденные двоичные суммы одно- битовых чисел.
36

4. После сложения 32-битовая ячейка будет хранить 16 пар двузнач- ных двоичных чисел (количества единиц в складываемых битах):
s&m=14415455 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 1 0 1 0 1 нечётные биты;
s>>1&m=54111555 0 1 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 чётные биты
+ ---------------------------------------------------------------------------- s=685269aa 0 1 1 0 1 0 0 0 0 1 0 1 0 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 1 0 1 0 Их сумма в виде
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/ 16 пар двузначных s=
01 10 10 00 01 01 00 10 01 10 10 01 10 10 10 10
двоичных чисел.
23= 5*1+9*2= 1 + 2 + 2 + 0 + 1 + 1 + 0 + 2 + 1 + 2 + 2 + 1 + 2 + 2 + 2 + 2 5. Значение самого младшего бита, исчезнувшее при сдвиге, не потре- буется уже никогда, так как оно вошло в сумму единиц, хранящуюся в самом правом двузначном двоичном числе.
6. Складываем двузначные числа, выделяемые новой маской, которая нацелена на выделение двузначных двоичных чисел:
0011001100110011001100110011001100110011001100110011001100110011
(или 33333333 в шестнадцатеричной записи). Результат её побито- вого умножения на текущее s — 8 двузначных двоичных чисел, на- ходящихся в нечётных (считая справа) парах битов:
s=685269aa 01 10 10 00 01 01 00 10 01 10 10 01 10 10 10 10 Находим числа m=33333333 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 равные коли-
------------------------------------------------------------ честву единиц s&m=20122122 00 10 00 00 00 01 00 10 00 10 00 01 00 10 00 10 в нечётных
/
/
/
/
/
/
/
/ парах двоичных
0010 0000 0001 0010 0010 0001 0010 0010
чисел текущего
2 0
1 2
2 1
2 2
значения S
7. После сдвига текущего s вправо на два бита содержимое всех чёт- ных двубитовых чисел переместится в соседние нечётные, так что умножение результата этого смещения на текущую маску выделит двузначные двоичные числа, имеющиеся в текущем s в чётных дву- битовых парах:
s>>2=1a149a6a 00 01 10 10 00 01 01 00 10 01 10 10 01 10 10 10 Сдвиг.
m=33333333 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 Выборка
----------------------------------------------------------------- чисел
(s>>2)&m=12101222 00 01 00 10 00 01 00 00 00 01 00 10 00 10 00 10
из
/
/
/
/
/
/
/
/ чётных
0001 0010 0001 0000 0001 0010 0010 0010
пар
1 0
2 0
1 0
0 0
1 0
2 0
2 0
2 37


8. Работа оператора s = s&0x33333333 + s >> 2&0x33333333; по- лучает в каждой из восьми тетрад количества единиц, которые хра- нятся в соответствующих тетрадах исходного значения N .
s&m=20122122 0010 0000 0001 0010 0010 0001 0010 0010
Сложение чисел
(s>>2)&m=12101222 0001 0010 0001 0000 0001 0010 0010 0010
из нечётных и
+
------------------------------------------------------- чётных пар s=32223344 0011 0010 0010 0010 0011 0011 0100 0100 3
+ 2 +
2 +
2 +
3 +
3 +
4 +
4
=
23.
9. Новая маска 00001111000011110000111100001111 (шестнадца- теричное 0f 0f 0f 0f ) для выделения нечётных тетрад s=32223344 0011 0010 0010 0010 0011 0011 0100 0100
Выделяем m=0f0f0f0f
0000 1111 0000 1111 0000 1111 0000 1111
нечётные
---------------------------------------------------- тетрады s&m=02020304 0000 0010 0000 0010 0000 0011 0000 0100
s>>4=03222334 0000 0011 0010 0010 0010 0011 0011 0100
Выделяем m=0f0f0f0f
0000 1111 0000 1111 0000 1111 0000 1111
чётные
-------------------------------------------------------
(s>>4)&m=03022304 0000 0011 0000 0010 0000 0011 0000 0100 10. Так что после сложения s = s&0x0f0f0f0f + s >> 4&0x0f0f0f0f ;
получаем в каждой из четырёх восьмибитовых значений количе- ства единиц, которые храняться в соответствующих байтах исход- ного значения N .
s&m=02020304 0000 0010 0000 0010 0000 0011 0000 0100
(s>>4)&m=03022304 0000 0011 0000 0010 0000 0011 0000 0100
+
------------------------------------------------------ s=
0000 0101 0000 0100 0010 0110 0000 1000
/
/
/
/
00000101 00000100 00000110 00001000 5
+
4
+
6
+
8
=
23 11. Очередная маска для выделения восьмибитовых чисел
00000000111111110000000011111111
(шестнадцатеричная запись 00f f 00f f ) даёт:
38
s= 00000101 00000100 00000110 00001000
m= 00000000 11111111 00000000 11111111
-------------------------------------- s&m= 00000000 00000100 00000000 00001000
s>>8= 00000000 00000101 00000100 00000110
m= 00000000 11111111 00000000 11111111
(s>>8)&m= 00000000 00000101 00000000 00000110 12. Так что после сложения s = s&00ff00ff + s >> 4&0x00ff00ff ;
получаем в каждом из двух шестнадцатибитовых двоичных числах количества единиц, которые храняться в соответствующих полусло- вах байтах исходного значения N .
s&m= 0000000 00000100 00000000 00001000
(s>>8)&m= 0000000 00000101 00000000 00000110
+
------------------------------------------- s= 0000000 00001001 00000000 00001110
/
/
000000000001001 0000000000001110 9
+
14
=
23 13. Наконец, используя маску 00000000000000001111111111111111
(шестнадцатеричная запись 00f f 00f f ) получаем возможность сло- жить два шестнадцатибитовых числа:
s= 0000000000001001 0000000000001110
m= 0000000000000000 1111111111111111
--------------------------------- s&m= 0000000000000000 0000000000001110
s>>16= 0000000000000000 0000000000001001
m= 0000000000000000 1111111111111111
(s>>16)&m= 0000000000000000 0000000000001001
s&m= 0000000000001001 0000000000001110
(s>>16)&m= 0000000000000000 0000000000001001
+
-------------------------------------
0000000000000000 0000000000010111 =1*2^4+7=16+7=23 39

1.2.9
Неявное преобразование типов в выражении
Неявное преобразование типов (т.е. при отсутствии в программе каких- либо явных инструкций по этому поводу) проводится, когда операнды разных типов:
a) участвуют в бинарной операции. Интуитивно ясно, что тип арифметического результата должен совпадать с типом того из опе- рандов, значение которого требует в машинном представлении боль- шего количества байт.
b) находятся слева и справа от знака операции присваива- ния. Иногда может потребоваться преобразование и более “длин- ного” данного в более “короткое”; например, получение значения типа int из типа double).
Для выбора направления неявного преобразования операндов опреде- ленно понятие старшинства или ранга типа. Всегда перед проведени- ем операции операнд низкого ранга преобразуется к типу операнда более высокого (старшего).
СИ
ФОРТРАН
Примечания complex(16)
самый старший complex( 8)
long double real(16)
double real( 8), double precision float real( 4) real long int integer(8)
unsigned long
Нет аналога unsigned
В ФОРТРАНе int integer(4), integer все целые unsigned int
Нет аналога unsigned типы short int integer(2)
знаковые unsigned short Нет аналога unsigned char integer(1)
самый младший
Часто человек забывает, что операции деления для операндов целых и вещественных типов – разные, хотя и записывются одним и тем же знач- ком (/). Вид операции выясняется из контекста: если оба операнда це- лого типа, то – деление целочисленное; если же хотя бы один из операн- дов – вещественный, то – вещественное. В приведённой ниже программе
40

демонстрируются как эффект целочисленного и вещественного делений при неявном приведении типов, так и эффект переполнения при попытке записи значения типа signed int в переменную типа signed char.
#include
#include
int main(void)
{ double x=1.0;
signed char m=3, n=5;
printf("
m/n*x=%f\n",m/n*x);
//
результат деления нацело m/n=0
printf(" x*(m/n)=%f\n",x*(m/n)); //
-"--"--"--"--"--"--"--"--"--"- printf("
m*x/n=%f\n",m*x/n);
//
m и n неявно преобразуются к double printf("
x*m/n=%f\n",x*m/n);
//
-"--"--"--"--"--"--"--"--"--"- x=m*50; printf(" x=m*50=%f\n",x);//
m --> int --> double n=m*50; printf(" n=m*50=%d\n",n);//
m --> int --> signed char,
return 0;
}
Результат работы программы:
m/n*x=0.000000
x*(m/n)=0.000000
m*x/n=0.600000
x*m/n=0.600000
x=m*50=150.000000
n=m*50=-106
Здесь переменные m и n однобайтовые знаковые, т.е. наибольшее поло- жительное число, которое может быть в них записано равно
(127)
10
= (01111111)
2
Но значение m ∗ 50 = 3 ∗ 50 = 150 = (10010110)
2
требует для своей двоичной записи (как положительное число) более одного байта (напри- мер, два):
(0000000010010110)
2
= 1 · 2 7
+ 1 · 2 4
+ 1 · 2 2
+ 1 · 2 1
= 128 + 16 + 4 + 2 = 150.
Поскольку его нужно трактовать как значение типа signed char, то старшая единица будет трактоваться как знак числа, а остальные биты как дополнительный код некоторого отрицательного числа. Для получе- ния его десятичного эквивалента все его биты следует инвертировать:
01101001 (получая обратный код) и добавить к последнему единицу:
(01101010)
2
= −(64 + 32 + 8 + 2) = −(106)
10 41

Аналогичная ФОРТРАН-программа (gfortran) и ее результаты:
program tstpriv1; implicit none integer(2) nn; integer(1) m, n; real(8) x x=1d0; m=3; n=5
!
РЕЗУЛЬТАТ:
write(*,*) ’
m/n*x=’,m/n*x
!
m/n*x=
0.00000000000000
write(*,*) ’ x*(m/n)=’,x*(m/n)
!
x*(m/n)=
0.00000000000000
write(*,*) ’
m*x/n=’,m*x/n
!
m*x/n=
0.59999999999999998
write(*,*) ’
x*m/n=’,x*m/n
!
x*m/n=
0.59999999999999998
x=m*50; write(*,*) ’
x=m*50=’,x
!
x=m*50=
150.000000000000
n=m*50; write(*,*) ’
n=m*50=’,n
!
n=m*50= -106
write(*,1000)
transfer(n,nn),&
! nn = 0000000010010110 = 150
&
transfer(n,nn)
1000 format(1x,’ nn= ’,B16.16 ,’ = ’,i4)
end
Здесь встроенная функция transfer(x, y[,size]) (см. [2]), преобразует данное x одного типа в данное y другого типа без изменения их фи- зического представления (т.е. значения двоичных разрядов обоих типов совпадают).
Неявные СИ-преобразования младших типов в старшие и обратно проводятся по определенным правилам. Новые старшие разря- ды целых типов для отрицательного значения заполняются знаком чис- ла, так как отрицательные числа записываются в дополнительном коде.
Преобразование целого в вещественное и обратно выполняется специ- альными функциями. Переход от значения старшего типа к значению младшего корректен, когда результат не оказывается вне диапазона, до- пускаемого младшим типом. Аналогично и переход от значения млад- шего типа к значению старшего корректен, если переводимое значение находится в пределах значений допускаемых старшим типом. Например,
попробуйте пропустить программу:
#include
int main()
//
Результат:
{
signed char p;
unsigned char q;
//
p=-126; q=p; printf(" p=%d q=%d\n",p,q); //
p=-126
q=130
q= 254; p=q; printf(" p=%d q=%d\n",p,q); //
p=-2
q=254
return 0;
}
42


1.2.10
Операции явного приведения типа
Смешивание в одном выражении операндов разных типов не рекоменду- ется, так как на их неявное преобразование тратится время. Кроме того,
невнимательное отношение к записи выражения чревато и побочными эффектами (см. предыдущий пункт). Поэтому лучше избегать смеши- вания типов в выражении, а при необходимости осуществлять явное приведение операндов к нужному типу.
В СИ для этой цели служит операция (имя нового типа), которая применяется к стоящему после закрывающейся круглой скобки операнду старого типа (но можно и так имя нового типа (операнд старого)):
#include
using namespace std;
int main(void)
{ int i=1, j=3;
float f1, f2;
double d1, d2;
f1=i / j; f2=(float)(i/j);
cout<<" f1="<f2="<f2=(float) i / (float) j;
cout<<"
f2="<f2=float(i)/j;
cout<<"
f2="<d1=i / j;
d2=(double)(i/j);
cout<<" d1="<d2="<d2=(double)i / (double)j; cout<<"
d2="<d2=(double)i / double(j); cout<<"
d2="<cout.setf(ios::floatfield, ios::scientific); // флаги: вещ., c порядком cout<<" f1="<1   2   3   4   5   6   7   8   9   ...   23

Почему на печати -127.0?
write(*,*) ’ int(cs)=’,int(cs)
write(*,*) ’ int(cd)=’,int(cd); write(*,*) ’ int(5.3)=’,int(5.3)
write(*,*) ’ int(5.3d0)=’,int(5.3d0)
are=4.7; aim=3.9; ca=cmplx(are,aim,8); write(*,*) ’ ca=’,ca write(*,’(’’
ca=’’,d23.15,’’+i*’’,d23.15)’) ca ca=cmplx(are,aim,4); write(*,*) ’ ca=’,ca ca=cmplx(are,aim);
write(*,*) ’ ca=’,ca bre=4.2d0; bim=7.3_8; cb=cmplx(bre,bim);
write(*,*) ’
cb=’,cb cb=cmplx(bre,bim,8); write(*,*) ’ 8: cb=’,cb cb=dcmplx(bre,bim); write(*,*) ’ cb=’,cb write(*,*) ’ dble(ca)=’,dble(ca); write(*,*) ’ dfloat(ca)=’,dble(ca)
write(*,*) ’ dble(cb)=’,dble(cb); write(*,*) ’ dfloat(cb)=’,dble(cb)
end
Компиляция testpriv1 на gfortran-компиляторе должна проводиться при включении -fno-range-check и -Wall (Почему?).
45

Назначение функций, которые встречаются в программе testpriv1:
Функция
Ее назначение aimag(z)
возвращает мнимую часть комплексного аргу- мента z
. Результат имеет вещественный тип той же длины, что и мнимая часть аргумента.
Тип аргументa complex или complex(8), или complex*16
cmplx(x[,y] [,kind])
возвращает для заданного значения аргумента x
любого числового типа, соответствующее значение типа complex разновидности kind. Аргумент y
y при наличии комплексного x
не требуется.
dble(x)
возвращает для заданного значения аргумента x
dfloat(x)
любого числового типа соответствующее значение вещественного типа удвоенной точности.
dcmplx(x,[,y])
может использоваться наряду с cmplx,
когда необходима удвоенная точность результата,
а указывать параметр разновидности лень.
float(x)
возвращает для заданного значения аргумента це- лого типа соответствующее значение вещественного.
int(a[,kind])
возвращает значение аргумента a
любого числового типа, приведенным к значению целого типа длиной kind байтов, путем отбрасывания дробной части.
При отсутствии параметра разновидности kind
(длины типа результата), последний имеет стандарт- ную длину принятую транслятором по умолчанию
(обычно это 4 байта, если при вызове транслятора не указана опция /4I2 или в тексте исходного модуля не применена директива $integer:2).
Единственное ограничение: величина аргумента не должна конфликтовать с диапазоном значений типа результата. (Подробнее см. например, [3]
[4]; [6]).
real(x,[kind])
возвращает для заданного значения аргумента любого числового типа, соответствующее значение вещественного разновидности kind.
transfer(x,y[,size])
возвращает значение аргумента x любого
(см., например, [2])
типа без изменения его физического предста- вления как значение типа аргумента y
(см. раздел 12. Функция передачи типа transfer).
Замечание:
При переходе с одного компилятора на другой полезно проверить,
что функции real и float при работе с данными типа complex дают результаты, согласующиеся на обоих компиляторах.
46