* C for Dummies * Aleph * 2000-07-18 * 003 *
-----[00]----------[Quote of Day]-----------------------------------------
Обычно люди работают по 8 часов в день; следовательно в
пересчете на дни это составит третью часть 365 дней,
содержащихся в году, т.е. примерно 122 дня. Но, как
правило, два дня в неделю - выходные или 104 дня за год.
Если вычесть это количество из 122, то останется всего 18
дней. Поскольку длительность отпуска составляет в среднем
10 дней, то остается 8. Но, именно столько дней в году
праздничных. Получается, что вообще никто не работает.
Scientific American
#1 January 1991
-----[01]----------[Blah-Blah-Blah]---------------------------------------
В недавнем выпуске рассылки CityCat "Головоломки" (rest.brain.golovolomki)
я обнаружил разбор забавной задачки на операции по модулю. Привожу ее
полностью.
Challenge:
----------
Один восточный правитель посчитал, что у него в диване слишком много
(больше семи) мудрецов и решил отрубить головы примерно половине из них.
Он объявил придворным мудрецам, что завтра утром их выстроят в затылок
друг другу, одев на каждого тюбетейку одного из семи цветов радуги. Каждый
будет видеть цвета тюбетеек только впереди стоящих и имеет право, будучи
спрошен, громко выкрикнуть название одного из цветов. Если это цвет его
тюбетейки, он получает награду, если нет - теряет голову. За ночь, мудрецы
разработали план, при котором не может погибнуть более одного из них.
Какой это план?
Solution:
---------
Мудрецы сговариваются обозначить каждый цвет числом от 0 до 6 (по
количеству цветов). Самым первым должен кричать последний в строю - у него
самая полная информация - он знает все цвета, кроме своего. Тогда он
складывает числовые значения всех цветов, делит это число с остатком на
семь (количество всех цветов), и выкрикивает полученный остаток (т.е.
цвет, который ему соответствует). Следующий, предпоследний, мудрец знает
сумму всех цветов для стоящих перед ним и легко может посчитать, какое
число от нуля до шести надо к этой сумме прибавить, чтобы получился тот
самый остаток, который выкрикнул последний. Это и есть номер его цвета. Он
выкрикивает свой цвет, и следующий, пред-предпоследний, мудрец знает теперь
сумму всех цветов перед собой плюс предпоследний и легко может вычислить,
какое число (от нуля до шести) надо прибавить, чтобы получился остаток,
который выкрикнул последний мудрец. Это и есть его цвет. Итд. Погибнуть
может только последний мудрец.
-----[02]----------[Topic]------------------------------------------------
Операции сдвига. Циклический сдвиг. Оператор Серла. Умножение целых чисел.
Модулярная арифметика. Действительные числа.
-----[03]----------[Body]-------------------------------------------------
Операции сдвига
---------------
Теперь, когда Вы уже фамильярны с битами, самое время поговорить о
сдвигах, точнее, операциях сдвига.
Начнем с десятичной системы.
Пусть у нас есть 4-х разрядный регистр, в котором записано число 1:
-----------------
| 3 | 2 | 1 | 0 |
-----------------
| 0 | 0 | 0 | 1 | = 0001
-----------------
Что произойдет, если мы сдвинем единицу на один разряд влево ?
-----------------
| 3 | 2 | 1 | 0 |
-----------------
| 0 | 0 | 1 | 0 | = 0010
-----------------
Поскольку основание системы счисления 10, сдвиг на один разряд равносилен
умножению на 10
Сдвинем еще раз:
-----------------
| 3 | 2 | 1 | 0 |
-----------------
| 0 | 1 | 0 | 0 | = 0100
-----------------
Произошло еще одно умножение на 10.
Мы даже можем ввести, по аналогии с оператором поворота, оператор сдвига S
(Shift):
S^0 = 1
S^1 = 10
S^2 = 100
итд.
А как это будет выглядеть при использовании двоичной системы счисления ?
В сущности, точно так же, только сдвиг будет обозначать умножение на 2:
S^0 = 1
S^1 = 2
S^2 = 4
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | = 00000001 = 1
---------------------------------
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | = 00000010 = 2
---------------------------------
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | = 00000100 = 4
---------------------------------
Операции сдвига часто используются, и в любом ассемблере обязательно есть
команды сдвига. В языках высокого уровня эта возможность, обычно
отсутствует, так как не предполагается, что программист захочет работать
на уровне битов. В языке C, предназначенном для системного
программирования, эта возможность поддерживается, хотя и не так хорошо,
как в ассемблере.
Очевидно, что сдвигать можно не только влево, но и вправо.
Я мог бы переписать три схемки вверху в обратном порядке, но, надеюсь, Вы
и так увидите, что сдвиг вправо равнозначен делению на основание системы
счисления.
Гм. У Вас еще не возникло чувство, что я чего-то не договариваю ?
В самом деле, на подтасованных схемках вверху нет ничего про знаковый
разряд. Между тем, как мы говорили, представление отрицательных чисел
довольно искусственно. Что делать со знаковым битом при сдвиге ?
Как обычно, есть две возможности:
ничего не делать и сделать что-нибудь.
В языке ассемблера реализованы обе, в С - только вторая.
Я не стану сейчас разбирать все замечательные команды сдвига,
реализованные в ASM, а только поясню две основные возможности.
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
Carry <- | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | <- Null
---------------------------------
Пусть, для простоты, у нас есть только одна единица в старшем (знаковом)
разряде.
При левом сдвиге (т.е. сдвиге на разряд влево) возникает перенос и единица
замещается нулем. В самый левый (младший) разряд довольно естественно
записать нуль. Знак числа (знаковый бит) потерян. Такой сдвиг называют
логическим (не сохраняющим знак, знаковый бит).
Рассмотрим теперь обратную ситуацию - правый сдвиг.
BEFORE:
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
? -> | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | -> Borrow
---------------------------------
Для симметрии, я поместил единицу и в младший разряд.
AFTER:
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| ? | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
---------------------------------
Что должно быть с старшем (седьмом) разряде ?
В двоичной системе у нас есть только две возможности: 0 и 1.
Если при правом сдвиге в старший разряд всегда заносится 0, то это
логический сдвиг (не сохраняющий знак, знаковый бит).
ЛОГИЧЕСКИЙ СДВИГ
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
---------------------------------
Но есть и другая возможность - сохранить знаковый бит (знак). Такой сдвиг
(сохраняющий знак), называется арифметическим.
АРИФМЕТИЧЕСКИЙ СДВИГ
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
---------------------------------
Если пойти на крайность и сдвинуть единичный знаковый бит влево на восемь
разрядов (потерять его), то мы получим:
ЛОГИЧЕСКИЙ СДВИГ
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---------------------------------
Регистр полностью обнулен.
АРИФМЕТИЧЕСКИЙ СДВИГ
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---------------------------------
Регистр полностью установлен.
Так вот, это важно запомнить:
В Turbo C всегда используется арифметический сдвиг.
На других машинах / других компиляторах это может быть иначе.
Для обозначения сдвига используются угловые скобки
'<<' - сдвиг влево
'>>' - сдвиг вправо
Важно, что пара угловых скобок рассматривается как один символ (оператор),
точно такой же, как, например, '+' или '*', но об этом мы поговорим
подробнее, когда перейдем к синтаксису языка С.
Циклический сдвиг
-----------------
Не сомневаюсь, Вам уже пришла в голову мысль свернуть сдвиг в кольцо.
Циклический сдвиг влево:
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
+--<- | S | 0 | 0 | 0 | 0 | 0 | 0 | 1 | -<--+
| --------------------------------- |
| |
+-------------------------------------------+
Циклический сдвиг вправо:
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---------------------------------
+-->- | S | 0 | 0 | 0 | 0 | 0 | 0 | 1 | ->--+
| --------------------------------- |
| |
+-------------------------------------------+
Естественно, возникает вопрос о знаком бите S.
Есть два варианта: поступить со знаковым битом, как с любым другим, или
оставить его на месте и не трогать вовсе (при этом он копируется в регистр
флагов). В ассемблере реализованы обе возможности, а в С, к сожалению,
циклический сдвиг отсутствует.
Легко понять, что операция циклического сдвига эквивалентна умножению на
оператор поворота и применяется, в сущности, для тех же целей.
Команды ассемблера так и называются:
ROL, ROR - ROtate Left, ROtate Right.
Операция сдвига часто используется при умножении на небольшие целые числа
взамен дорогостоящей (по времени выполнения) операции умножения. Правда, с
появлением быстродействующих арифметических сопроцессоров это стало почти
неактуально. Во всяком случае, если речь идет о критической по времени
выполнения части кода, всегда стоит проверить - в самом ли деле Ваша
"ручная" оптимизация лучше той, что предложил оптимизирующий компилятор.
Оператор Серла
--------------
До сих пор мы рассматривали действия над отдельными разрядами, но иногда
используются и более сложные конструкции.
В первом выпуске мы рассматривали операцию '^' (XOR) и говорили о том, что
сложение и вычитание по mod 2 неразличимы.
-------------
|XOR| 0 | 1 |
-------------
| 0 | 0 | 1 |
-------------
| 1 | 1 | 0 |
-------------
Между тем, очевидно, было бы удобно иметь пару операций по mod 2,
аналогичных обычному сложению и вычитанию. Этого можно добиться, если
использовать пары бит (диады) в качестве единого операнда.
При этом возникает возможность составить несколько различных вариантов
таблиц истинности. Наибольшее распространение получил следующий:
Диадное cложение Диадное вычитание
(по mod 2) (Операция Серла)
+----+----+----+----+----+ +----+----+----+----+----+
| + | 00 | 01 | 10 | 11 | | - | 00 | 01 | 10 | 11 |
+----+----+----+----+----+ +----+----+----+----+----+
| 00 | 00 | 01 | 10 | 11 | | 00 | 00 | 10 | 11 | 01 |
+----+----+----+----+----+ +----+----+----+----+----+
| 01 | 01 | 00 | 11 | 10 | | 01 | 11 | 01 | 00 | 10 |
+----+----+----+----+----+ +----+----+----+----+----+
| 10 | 10 | 11 | 00 | 01 | | 10 | 01 | 11 | 10 | 00 |
+----+----+----+----+----+ +----+----+----+----+----+
| 11 | 11 | 10 | 01 | 00 | | 11 | 10 | 00 | 01 | 11 |
+----+----+----+----+----+ +----+----+----+----+----+
Я использовал обычные обозначения для '+' и '-', так как просто не имею
специальных символов на клавиатуре, но Вы должны помнить, что речь идет
именно о диадных, а не битовых операциях.
После того, как Вы вдоволь нагляделись на таблицы истинности, у Вас
неизбежно должен возникнуть вопрос:
- А на кой черт нужны все эти сложности ?
Очень просто. Это - плата за скорость. Помните, мы говорили о выгодности
операций без переноса. Если каким-то образом от обычного сложения и
вычитания перейти к сложению и вычитанию без переноса, то можно получить
значительное ускорение вычислений. В системах реального времени (таких,
как управление движением самолетов), это обстоятельство может иметь
решающее значение.
Умножение целых чисел
---------------------
Вероятно, Вам приходилось слышать, что школьный метод умножения
"столбиком" далеко не оптимален и профессиональные вычислители пользуются
другими способами.
У меня, к сожалению, нет возможности уточнить названия этих книг, но
выглядеть это должно примерно так:
1. Берман "Приемы счета"
2. Трахтенгерц "Система быстрого счета по Трахтенгерцу"
Например, "школьным" методом 77 на 13 умножается так:
77
*
13
---
231
+
77
---
1001
Рациональный метод умножения двухзначных чисел выглядит по-другому:
7 7
| X |
1 3
-----
21 ; Перемножаем цифры в младшем разряде
7 ; Перемножаем цифры в старшем разряде
21 ; Перемножаем "накрест" цифры младшего
7 ; и старшего разряда
-----
1001 ; Все суммируем
Хотя само число шагов больше, на каждом из них мы оперируем только с
одноразрядными числами и само умножение получается проще.
Кстати, я думаю "1001 ночь" (в оригинале: "Тысяча ночей и одна
ночь") тоже не случайное число, а связано с древней магией чисел.
Тем не менее, изложенные в этих книгах оригинальные методы ускорения
вычислений почти неприменимы в компьютерах - однородный способ вычислений
важнее специальных приемов "ad hoc".
Так что компьютер вычисляет примерно так, как это делаем мы на бумажке,
также используя сдвиг и перенос из разряда в разряд.
Фактически, достаточно уметь умножать многоразрядное число на
одноразрядное или даже одноразрядное на одноразрядное.
Рассмотрим, к примеру, как может выглядеть умножение 2 на 2 в двоичной же
системе счисления. Я не стану уверять Вас, что математический сопроцессор
работает именно таким образом - я просто не знаю.
00000010
*
00000010
--------
00000000
00000010
00000000
00000000
00000000
00000000
00000000
00000000
---------------
000000000000100
Существует, однако, интересный способ вычислений, основанный на операциях
без переноса - по модулю. Как я уже упоминал, он использовался еще в
календаре древних майя (Система Остаточных Классов).
Подробности можно найти в Д. Кнут "Искусство программирования для ЭВМ" т.
2, гл. 4.3.2. "Модулярная арифметика", а сейчас я только кратко изложу
идею метода.
Модулярная арифметика
---------------------
Представьте, что имеется набор попарно взаимно простых (не имеющих общих
делителей) модулей и операции производятся не над самими числами, а над их
остатками по этим модулям.
Следуя Кнуту и ограничиваясь только тремя модулями m1 - m3, примем
обозначения:
U1 = U mod m1 V1 = V mod m1
U2 = U mod m2 V2 = V mod m2
U3 = U mod m3 V3 = V mod m3
Векторы - наборы чисел (U1,U2,U3) и (V1,V2,V3) - будут являться машинным
(модулярным) представлением исходных чисел U и V.
Тогда для сложения, вычитания и умножения, мы можем записать:
(U1,U2,U3) + (V1,V2,V3) = ((U1 + V1) mod m1,(U2 + V2) mod m2,(U3 + V3) mod m3)
(U1,U2,U3) - (V1,V2,V3) = ((U1 - V1) mod m1,(U2 - V2) mod m2,(U3 - V3) mod m3)
(U1,U2,U3) * (V1,V2,V3) = ((U1 * V1) mod m1,(U2 * V2) mod m2,(U3 * V3) mod m3)
Все действия выполняются над остатками, без переноса из разряда в разряд.
Для соблюдения однозначности результата, достаточно, чтобы числа U и V
были не слишком большими, точнее говоря:
0 <= U <= V <= m1 * m2 * m3
Например, если
m1 = 7, m2 = 11 и m3 = 13
(все простые), то
m1 * m2 * m3 = 1001
Рассмотрим числовой пример.
Пусть U = 248 и V = 365
(В древнееврейской традиции эти числа имеют специальное мистическое
значение количества повелений и запретов - по числу дней в году и числу
костей в человеке)
U1 = 248 mod 7 = 3
U2 = 248 mod 11 = 6
U3 = 248 mod 13 = 1
V1 = 365 mod 7 = 1
V2 = 365 mod 11 = 2
V3 = 365 mod 13 = 1
(3,6,1) + (1,2,1) = ((3 + 1) mod 7,(6 + 2) mod 11,(1 + 1) mod 13)
(3,6,1) - (1,2,1) = ((3 - 1) mod 7,(6 - 2) mod 11,(1 - 1) mod 13)
(3,6,1) * (1,2,1) = ((3 * 1) mod 7,(6 * 2) mod 11,(1 * 1) mod 13)
(3,6,1) + (1,2,1) = (4 mod 7,8 mod 11,2 mod 13) = (4,8,2)
(3,6,1) - (1,2,1) = (2 mod 7,4 mod 11,0 mod 13) = (2,4,0)
(3,6,1) * (1,2,1) = (3 mod 7,12 mod 11,1 mod 13) = (3,1,1)
Найдем теперь результат вычислений в позиционной системе счисления,
восстановив значения по остаткам.
Гм. На самом деле, это не так просто.
Но Вы можете легко убедиться, что вектору (4,8,2) соответствует число 613
= 248 + 365 и вектору (2,4,0) соответствует число 884, являющееся
дополнительным к 248 - 365 = -117 по модулю 1001.
Вектору (3,1,1) отвечает число 90520 = 248 * 365.
Кстати, для всех этих вычислений, рекомендую неплохой калькулятор:
http://www.fastsoft.lg.ua/new_calc.zip (212,538)
Число 613 имеет специальное название, известное каждому религиозному
еврею, про остальные числа мне ничего не известно.
Итак, взамен сложных действий с большими числами, мы обошлись вычислениями
с небольшими остатками - вычетами по модулю.
Правда, заключительный этап - возврат в позиционную систему счисления
достаточно сложен и я сейчас не буду на нем останавливаться.
Основывается он на знаменитой "Китайской теореме об остатках", подробности
можно найти там же у Кнута т. 2 "Получисленные алгоритмы" гл. 4.3.2.
После разбора этого примера, использование модулярной системы, наверное,
представляется Вам полным идиотизмом, но, на самом деле, это не так.
Есть по крайней мере, две области, где выгодна модулярная арифметика:
1. Параллельные вычисления. Многопроцессорные системы, где допускается
одновременное выполнение (распараллеливание) операций. Отсутствие
взаимных переносов существенно облегчает построение таких систем, где
операции по каждому модулю выполняются независимо. Это особенно важно
для систем реального времени
2. Вычисления с высокой точностью. Существуют приложения, где ошибки
округления недопустимы. Оперировать же с огромными цифрами с
фиксированной точкой весьма непросто. Выход состоит в переходе к
модулярной арифметике, операциях с относительно небольшими остатками по
модулю и возврату в позиционную систему счисления.
Подробнее можно прочитать здесь: Р.Грегори, Е. Кришнамурти
"Безошибочные вычисления. Методы и приложения" М. Мир, 1988
Действительные числа
--------------------
До сих пор мы говорили только о представлении и действиях над целыми
числами.
Как быть, если нам требуется оперировать с дробями?
Рассмотрим, например, дробь 1/7. Каким образом она может быть представлена
в компьютере ?
В десятичном представлении 1/7, это бесконечная периодическая дробь
0.(142857)
Число в скобках называется периодом дроби. Однако, если Вы попытаетесь
разделить 1 на 7 используя калькулятор, то получите примерно такой
результат:
0.1428571429
Вместо бесконечной периодической дроби - довольно грубое приближение.
Почему так ?
Вспомните, когда мы рисовали разрядную сетку, то подсчитывали, какое
максимальное число можно в ней записать. Для одного восьмиразрядного байта
это 255, а если отвести старший бит под знак, то всего 127.
Конечно, для рациональных чисел, таких как
1/7 = 0.(142857)
или
1/17 = 0.(0588235294117647)
или
1/29 = 0.(0344827586206896551724137931)
являющихся бесконечными периодическими дробями, можно пуститься на
хитрость и хранить их в виде пары целых чисел - числителя и знаменателя.
В программах, где требуется очень высокая точность вычисления, так
часто и поступают. Но как быть с иррациональными числами, такими как
PI = 3.14159265358979323846
E = 2.71828182845904523536
SQRT2 = 1.41421356237309504880
Заметьте, что все это приближенные значения !
И вот здесь перед нами неразрешимая проблема: как всякое реальное
физическое устройство, компьютер способен работать только с конечным
числом цифр. Оставьте +/- бесконечность математикам и примиритесь с
ошибками округления и тем, что замечательные алгоритмы из книжек
совершенно непригодны на практике - арифметика конечной точности диктует
свои законы.
Прежде всего, нам необходимо договориться, как вообще записывать
и хранить такие неудобные числа.
Поскольку единицей хранения (адресации) является байт, то для хранения
больших чисел нам потребуется несколько байт. В компьютерах с двоичной
арифметикой удобно, чтобы это "несколько" было степенью двойки: 2, 4, 8
Соответственно, мы получаем разрядность 16, 32 и 64 бита для целых чисел.
Но понятно, что хранить подобные числа как целые, очень неудобно: для
одних целей требуются очень большие числа, такие, как скорость света в
вакууме или расстояние от Земли до Солнца, а для других - очень маленькие,
такие как масса электрона или диаметр атома водорода.
Интересно, что древнеиндийская космогония имела подходящие термины для
обозначения размеров и промежутков времени именно таких масштабов - от
атомных до астрономических.
На этот счет существует немало интересных гипотез и спекуляций и не
исключено, что атомное оружие отнюдь не новинка. (За подробностями отсылаю
Вас к индийскому эпосу "Махабхарата").
Очевидно, первая мысль, какая приходит в голову, это разделить само число
и его масштабный множитель. 10 фемтосекунд и 10 световых лет - это только
10 и указание на единицу измерения (масштаб).
Тогда, например, в одном байте можно записывать само число (в нашем случае
10), а в другом байте - масштабный множитель (в нашем случае, фемтосекунда
или световой год).
Эти две части называются, соответственно, дробной частью числа (мантисса)
и показателем (экспонента).
На самом деле, разбиение разрядной сетки может быть иным (и размеры этих
двух частей обычно различны).
Поскольку положение десятичной точки в дроби при такой записи не
фиксируется, то их называют числами с плавающей (float) точкой.
Представление чисел с плавающей точкой и действия над ними определяются
IEEE Floating-Point Arithmetic Standard (ANSI/IEEE Std 754-1985)
Поскольку, скорее всего, Вам не придется вникать в детали внутреннего
представления чисел с плавающей точкой, то просто замечу, что в языке
С поддерживается два формата - с обычной и удвоенной точностью. Им
соответствуют типы float (4 байта) и double (8 байт).
-----[04]----------[Homework]---------------------------------------------
Вопросы не обязательно основаны на материалах выпуска и, как это обычно и
бывает в жизни, могут потребовать от Вас самостоятельного исследования.
1. Используя только операции сдвига и сложения, реализуйте умножение на 10
в двоичной системе счисления. Зависит ли Ваша схема умножения от
разрядности регистра. Можете ли Вы так изменить эту схему, чтобы
реализовать деление на 10 ?
2. Симметричная сумма. (по кн. Кордемский Б.А. "Математическая смекалка")
Возьмите многозначное число (состоящее более чем из одной цифры) и
сложите его с числом, полученным из первого, перестановкой цифр в
обратном порядке.
Например, для трехзначного числа XYZ, имеем:
SUM = XYZ + ZYX
Опыт показывает, что повторив это действие несколько раз, можно
получить цифровой палиндром - число, которое одинаково читается слева
направо и справа налево.
Примеры:
38 + 83 = 121
139 + 931 = 1070
1070 + 0701 = 1771
48017 + 71084 = 119101
119101 + 101911 = 221012
221012 + 210122 = 431134
Существует ли число, которое никогда не приведет к симметричному
результату ?
Попробуйте число 196.
Можете ли Вы дать аналитическое решение, используя Ваши знания
о системах счисления ?
-----[05]----------[Books]------------------------------------------------
Есть много хороших книг, но сейчас я хочу назвать нескольких авторов, без
изучения книг которых, программист никогда не выйдет за уровень простого
ремесленника.
Н. Вирт (автор языков Паскаль и Модула)
Удивительно легко пишет о сложных вещах. Без преувеличения, мой
любимый автор. Потрясающее умение отделить проблему от всякой
шелухи, так что решение становится почти очевидным. Книги написаны
так хорошо, что доступны даже начинающим.
Э. Дейкстра (Создатель дисциплины "Программирование")
Его не всегда понимают даже коллеги. Его книги столь же блестящи,
как и трудны. Начинать надо не с них - но всегда держать под рукой.
Когда Вы начнете их понимать, то сможете сказать о себе:
"Я - Программист !"
Ч. Хоар (Автор Структурного Программирования)
Не в первый день, но надо прочесть. Без осознания этих концепций
хороших программ не получится.
Д. Кнут (автор энциклопедии "Искусство программирования для ЭВМ")
Это не книги для чтения на ночь. Но они потребуются, когда вы
действительно начнете делать что-то стоящее и думать не о том, как
заставить программу работать, а как заставить ее работать наилучшим
образом.
-----[06]----------[Glossary]---------------------------------------------
IEEE - (American) Institute of Electrical and Electronic Engineers
-----[07]----------[Info]-------------------------------------------------
Вопросы и замечания принимаются по адресу: aleph@canada.com
Предыдущие выпуски доступны со страницы архива:
http://www.subscribe.ru/archive/comp.prog.c4dummies/
-----[--]----------[The End]----------------------------------------------
© Gazlan 2009 * gazlan@yandex.ru
|