Чарльз Петцольд. КОД тайный язык информатики

Глава 10. Логика и переключатели

Явившись в зоомагазин, вы сказали продавцу: «Мне нужен стерилизованный (С) кот (М) белого (Б) или рыжего (Р) окраса; или стерилизованная кошка (Ж) любого окраса, кроме белого; или я возьму любую из имеющихся у вас черных кошек (Ч)», — и продавец составил такое выражение:

Каждый символ «×» соответствует месту схемы, где два переключателя (или две группы переключателей) соединены последовательно, каждый символ «+» — месту схемы, в котором два переключателя (или две группы переключателей) соединены параллельно.

(М × С × (Б + Р)) + (Ж × С × (1 − Б)) + Ч

или так

(С × (М × (Б + Р)) + (Ж × (1 − Б))) + Ч

  • символ + соответсвует параллельному соединению т.е. ИЛИ
    • т.е. кошка/кот не может быть двух полов одновременно, поэтому или так или иначе т.е. что-то одно
  • символ x соответсвует параллельному соединению т.е. И
    • т.е. кошка/кот может быть белая/черная/рыжая и одновременно женского/мужского пола, тут нет противоречий
  • 1 − Б означает - не белая, т.е. НЕ

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

Глава 10. Логика и переключатели

Логика и переключатели (www.falstad.com/circuit)

Глава 11. Логические вентили

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

Логические вентили

Логические вентили OR, AND, XOR (www.falstad.com/circuit)

AND в логике и программировании означает И (логическое умножение или конъюнкция). Это логическая операция, которая выдает истину (True), только если оба операнда истинны. Если хотя бы один из операндов ложен, то результат — ложь.

AND01
000
101

OR Это логическая операция, которая выдает истину (True), если хотя бы один из операндов истинен.

OR01
001
111

XOR (от англ. Exclusive OR/Исключающее ИЛИ). Это логическая операция, которая выдает истину (True), если только один из операндов истинен, и ложь (False), если оба операнда одинаковы (оба истинны или оба ложны).

XOR01
001
110

NOT (Инвертор) Инвертирует входное значение. Истина становится Ложью, и наоборот.

0 -> NOT -> 1

1 -> NOT -> 0

NAND (Not-AND/И-НЕ) Противоположность AND. Выдает Ложь, только если оба входа Истинны. Т.е. сперва применяется операция AND и к результату ее применяется операция NOT: 1 AND 1 = 1 NOT = 0

NAND01
011
110

NOR (Not-OR/ИЛИ-НЕ) Противоположность OR. Выдает Истину, только если оба входа Ложны. Т.е. сперва применяется операция OR и к результату ее применяется операция NOT: 0 OR 0 = 0 NOT = 1

NOR01
010
100

XNOR (Exclusive-NOR/Исключающее ИЛИ-НЕ). Противоположность XOR. Выдает Истину, если оба входа одинаковы (оба Ложны или оба Истинны).

XNOR01
010
101

Глава 12. Двоичный сумматор

При сложении двух двоичных чисел получается бит суммы который представлен как NOR (Not-OR/ИЛИ-НЕ) и бит переноса который представлен как AND. Поэтому можно комбинировать вентили И и Искл-ИЛИ для сложения двух двоичных цифр A и B.

полусумматор

полусумматор на реле

схема полусумматора и полного сумматора (www.falstad.com/circuit)

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

 1111
+
 1111
-----
11110

Мы можем использовать полусумматор только для сложения цифр в правом крайнем столбце: 1 плюс 1 равно 0, 1 переносится. В случае со вторым столбцом справа нам, по сути, нужно сложить три двоичные цифры из-за переноса. И это касается всех остальных столбцов. Каждая последующая операция сложения двух двоичных цифр может включать бит переноса из предыдущего столбца.

Для сложения трех двоичных цифр понадобятся два полусумматора и вентиль ИЛИ, соединенные следующим образом.

полный сумматор

полный сумматор на реле

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

Вход AВход BВход для переносаВыход для суммыВыход для переноса
00000
01010
10010
11001
00110
01101
10101
11111

Для создания сумматора потребуются 144 реле. Вот как я это понял: для каждого вентиля И, ИЛИ и И-НЕ требуются по два реле. Таким образом, вентиль Искл-ИЛИ состоит из шести реле. Полусумматор — это вентиль Искл-ИЛИ и вентиль И, поэтому для его создания необходимы восемь реле. Каждый полный сумматор — два полусумматора и вентиль ИЛИ, то есть 18 реле.

Нам нужны восемь полных сумматоров для создания 8-битной машины, или 144 реле.

Пример: 5 (0101) + 6 (0110) = 11 (1011)

 0101
+
 0110
_____
 1011

8-ми битный сумматор на реле

8-ми битный сумматор на реле

8-ми битный сумматор сложения на реле (www.falstad.com/circuit)

8-ми битный сумматор.png

8-ми битный сумматор сложения (www.falstad.com/circuit)

Глава 13. А как насчет вычитания?

Используется сложение с отрицательными числами, представленных в дополнительном коде.

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

 уменьшаемое
-
 вычитаемое
___________
 разность
 253
- 
 176
____
  77

Начнем решение с крайнего правого столбца. Сначала мы замечаем, что 6 больше 3, поэтому нам нужно занять 1 у 5, а затем вычесть 6 из 13, в результате чего получается 7. Мы помним, что заняли 1 у 5, поэтому вместо 5 имеем 4, что меньше 7, поэтому занимаем 1 у 2, вычитаем 7 из 14 и получаем 7. Мы заняли 1 у 2, поэтому у нас есть только единица, из которой вычитаем 1 и получаем 0. Наш ответ — 77.

Как же заставить группу логических вентилей следовать такой странной логике? Не будем даже пытаться. Вместо этого используем небольшой трюк, позволяющий вычитать без заимствования. Вместо вычитания мы будем суммировать но предварительно поработаем с вычитаемым, его следует побайтово инвертировать (дополнение до единицы) и прибавить 1.


 253
- 
 176
____
  77

Дополнительный код: 
инвертируем вычитаемое 176 и прибавляем 1:
 176 => !10110000 => 01001111
 1 => 00000001 
результат: 01001111 + 00000001 = 01010000

теперь к уменьшаемому 253 (11111101) прибавляем вычитаемое: 

 11111101
+
 01010000
 ________
101001101

Отбрасываем старший (переполняющий) разряд за пределами 8 бит, остается 01001101 что соответсвует 77

fn main() {
    let a:u8 = 253; 
    let b:u8 = 176; 
    println!("{a} ({:08b}) - {b} ({:08b}) = {} ({:08b})", a,b,a-b,a-b);
    println!("---");
    println!("Дополнительный код  : !{b} ({:08b}) + 1 ({:08b}) = {:08b}", !b ,1,-(b as i8));
    println!("Выполняем вычисление: {a} ({:08b}) + !{b} ({:08b}) = {} ({:08b})", a, -(b as i8),a as i8 + (-(b as i8)) ,a as i8 + (-(b as i8)) );
}

Еще пример:

 56  
-
 24  
_________
 32  
 
Дополнительный код: 
инвертируем вычитаемое 24 и прибавляем 1:
 24 => !00011000 => 11100111
 1 => 00000001 
результат: 11100111 + 00000001 = 11101000
                                  
теперь к уменьшаемому прибавляем вычитаемое:

 00111000
+
 11101000
_________
100100000

Отбрасываем старший (переполняющий) разряд за пределами 8 бит, остается 00100000 что соответсвует 32
 
fn main() {
    let a:u8 = 56; 
    let b:u8 = 24; 
    println!("{a} ({:08b}) - {b} ({:08b}) = {} ({:08b})", a,b,a-b,a-b);
    println!("---");
    println!("Дополнительный код  : !{b} ({:08b}) + 1 ({:08b}) = {:08b}", !b ,1,-(b as i8));
    println!("Выполняем вычисление: {a} ({:08b}) + !{b} ({:08b}) = {} ({:08b})", a, -(b as i8),a as i8 + (-(b as i8)) ,a as i8 + (-(b as i8)) );
}

Инверсию вычитаемого реализуем через XOR (от англ. Exclusive OR/Исключающее ИЛИ), а прибавление 1 реализуем через HIGH сигнал разряда переноса в первый сумматор.

8-ми битный сумматор сложения и вычитания

8-ми битный сумматор сложения и вычитания (www.falstad.com/circuit)

Глава 14. Обратная связь и триггеры

Волшебство наблюдается, когда вы размыкаете верхний переключатель. Поскольку выход вентиля ИЛИ-НЕ — 0, если один из входов — 1, выход левого вентиля ИЛИ-НЕ не изменяется, и лампочка не гаснет.

Цикл перехода состояний Триггера:

stepABOutOut NOR 1-й
1.LLH
2.LHLH
3.LLLH
4.HLHL
1.LLH
...

Странность этой схемы заключается в том, что, когда оба переключателя разомкнуты, иногда лампочка горит, иногда — нет. Можно сказать, что эта схема имеет два устойчивых состояния, когда оба переключателя разомкнуты. Называется такая схема триггером.

Триггер сохраняет информацию, «помнит». В частности, показанный ранее триггер помнит, какой переключатель был замкнут последним. Если вы столкнулись с таким триггером и видите, что лампочка горит, можете предположить, что последним был замкнут верхний переключатель; если лампочка не горит — ​нижний.

trigger

Обратная связь и триггеры (www.falstad.com/circuit)

  • Состояние 1. L(A) L(B) = H - Когда на вентиле A LOW, и на вентиле B тоже LOW.

Обьясниение: так как вентиль B выключен LOW то на выходе второй группы NOR (т.е. у реле NOT) результат HIGH и благодаря ему происходит питание первого реле первой группы NOR что обеспечивает результирующий выход этой группы LOW (т.е. у реле NOT) и даже если переключить вентиль A в HIGH то ничего не изменится, результат триггера будет прежним HIGH

  • Состояние 2. L(A) H(B) = L - Когда на вентиле A LOW, а на вентиле B HIGH.

Обьясниение: вентиле B подавая HIGH сигнал закрывает реле NOT на выходе второй группы NOR и вообще результат становится LOW Перекрывается питание первых групп реле что в свою очередь открывает питание для реле NOT который теперь питает первое реле второй гпуппы NOR, этот момент и есть магия, эта питающая линия на следующем шаге обеспечит питание для реле NOT второй группы что заставит его находится в LOW режиме.

  • Состояние 3. L(A) L(B) = L - Когда на вентиле A LOW, и на вентиле B тоже LOW.

Обьясниение: выключая вентиль B мы лишь отключаем второе реле второй группы, а первое продолжает питать NOT реле удерживая его в LOW режиме.

  • Состояние 3. H(A) L(B) = H - Когда на вентиле A HIGH, а на вентиле B LOW.

Обьясниение: теперь если вентиль A HIGH, то это даст на выходе первой группы NOR результат LOW что заставит включиться в HIGH на общем выходе.

Они обеспечивают память схемы, сохраняющую историю того, что произошло ранее. Представьте, что значит считать, не обладая памятью. В этом случае вы не знаете, какое число задумали, какое число следует к нему прибавить! Точно так же схема, которая производит подсчет (описанная далее), требует наличия триггеров. Существуют два различных типа триггеров. Тот, что я показал выше, является самым простым и называется RS-триггером (Reset/Set, сброс/установка).

Схема D-триггер со срабатыванием по уровню. Буква D означает «данные» (Data). Срабатывание по уровню указывает на то, что триггер сохраняет значение входа «Данные» в тот момент, когда сигнал на входе «Запомнить этот бит» достигает определенного уровня, в данном случае 1.

D-триггер

D-триггер (www.falstad.com/circuit)

Эта схема также называется защелкой D-типа со срабатыванием по уровню; термин означает, что схема «запирает» один бит данных и удерживает его для дальнейшего использования. Эту схему также можно рассматривать в качестве ячейки памяти емкостью один бит. В главе 16 я продемонстрирую способ соединения большого количества таких триггеров для обеспечения памяти большего объема.

Когда вход Clk равен 1, выходной сигнал Q совпадает с входным сигналом «Данные». Когда значение входа Clk меняется на 0, выход Q сохраняет последнее значение входа «Данные». Дальнейшие изменения входного сигнала «Данные» не влияют на выходы до тех пор, пока значение входа Clk снова не изменится на 1. Вот таблица логики для триггера.

вход Dвход Clkвыход Qвыход !Q
0101
1110
X0QQ

X означает «неважно». Значение входа «Данные» неважно, поскольку в случае, когда значение входа «Запомнить этот бит» равно 0, выход Q остается прежним.

Глава 16. Сборка памяти

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

Сколько переключателей нужно? Если хотим выбрать один из восьми элементов, потребуются три переключателя. Три переключателя (Sel 0, Sel 1, Sel 2) могут представлять восемь разных значений: 000, 001, 010, 011, 100, 101, 110 и 111, которые будут соответвовать вомьми нашим вводам данных (D0...D7).

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

Селектор «8 на 1» состоит из трех инверторов, восьми четырехвходовых вентилей И и одного восьмивходового вентиля ИЛИ.

Select

устройтво выбра источника данных (www.falstad.com/circuit)

Входы Select позволяют выбрать, сигнал какого входа для данных появится на выходе. Например, если сигналы Select равны 000, то выходной сигнал совпадает с D0. Если входы Select равны 111, то выходной сигнал совпадает с D7. Если входы Select — ​101, выходной сигнал совпадает с D5.

Sel0Sel1Sel2Выход Q
000D0
100D1
010D2
110D3
001D4
101D5
011D6
111D7

Если мы хотим выбрать сигнал HIGH от D5, то выбрав Sel2=1, Sel1=0, Sel0=1 мы уже создадим три сигнала HIGH для вентиля И к которому идет сигнал от D5 и плюс сам сигнал HIGH от D5 что и даст полных 4 сигнала HIGH

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

Итак, наша задача наполовину решена. Теперь, когда мы определились с устройством на выходе, давайте займемся входом. На входе мы имеем сигналы «Данные» и «Запись». Входы «Данные» защелок можно соединить между собой. Однако мы не можем сделать то же самое с сигналами «Запись», поскольку хотим записывать данные отдельно, следовательно, должны подавать один сигнал «Запись» на одну (и только одну!) защелку.

Теперь нужна другая схема, которая немного похожа на селектор «8 на 1», но выполняет прямо противоположное действие. Эта схема называется дешифратор «3 на 8».

Дешифратор «3 на 8» имеет восемь выходов. В любой момент все эти выходы равны 0, кроме одного, который выбран входными сигналами Sel0, Sel1 и Sel2. Значение этого выхода совпадает со значением входа «Ввод данных».

дешифратор "3 на 8"

дешифратор «3 на 8» (www.falstad.com/circuit)

Sel0Sel1Sel2Выход Q
000D0 будет иметь значение data, все остальные выходы 0
100D1 будет иметь значение data, все остальные выходы 0
010D2 будет иметь значение data, все остальные выходы 0
110D3 будет иметь значение data, все остальные выходы 0
001D4 будет иметь значение data, все остальные выходы 0
101D5 будет иметь значение data, все остальные выходы 0
011D6 будет иметь значение data, все остальные выходы 0
111D7 будет иметь значение data, все остальные выходы 0

Эта конфигурация защелок иногда называется памятью с записью/чтением, но чаще — ​памятью с произвольным доступом, или произвольной выборкой (random access memory, RAM). Эта конкретная конфигурация RAM хранит восемь отдельных однобитных значений.

Устройство называется памятью, потому что оно сохраняет информацию. Возможность чтения/записи говорит о том, что вы можете сохранить новое значение в любой защелке (записать значение), а также узнать, что хранится в каждой из защелок (прочитать значение). Термин «произвольный доступ» означает, что запись и считывание информации из защелок могут осуществляться путем изменения входных сигналов «Адрес».

Описанная выше конфигурация RAM часто называется массивом RAM. Этот конкретный массив RAM организован по схеме, иногда сокращенно обозначаемой «8 × 1» — ​восемь однобитных значений. Чтобы определить общее количество битов, которые можно сохранить в массиве RAM, нужно перемножить эти два числа.

массив RAM "8x1"

массив RAM "8x1"

массив RAM "8x1" (www.falstad.com/circuit)

Массивы RAM можно комбинировать. Например, объединить два массива RAM «8 × 1».

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

Как вы помните, 8-битная защелка использует триггеры для хранения 8-битного значения. Чтобы использовать это устройство, на мгновение нажмите кнопку «Очистка» для обнуления содержимого защелки. Затем с помощью переключателей введите первое число. Сумматор просто прибавит его к нулевому значению на выходе защелки, поэтому результатом будет введенное число. При нажатии кнопки «Сложить» это число сохранится в защелке и отобразится лампочками. Теперь с помощью переключателей введите второе число. Сумматор прибавит его к уже сохраненному в защелке. Нажатие кнопки «Сложить» снова приводит к сохранению общей суммы в защелке и ее отображению с помощью лампочек. Так вы можете сложить множество чисел, отображая общую сумму, которая, правда, ограничена числом 255 при использовании восьми лампочек.

Массивы RAM можно комбинировать. Например, объединить два массива RAM «8 × 1». Этот массив RAM будет хранит восемь значений, размер каждого из которых составляет два бита.

Кроме того, два массива RAM «8 × 1» можно объединить как отдельные защелки, используя селектор «2 на 1» и дешифратор «1 на 2». Сигнал «Выборка», который подается как на дешифратор, так и на селектор, по сути, выбирает один из двух массивов RAM «8 × 1». На самом деле он является четвертой адресной линией. Таким образом, мы имеем дело с массивом RAM «16 × 1».

array RAM 16x1

array RAM 16x1 (www.falstad.com/circuit)

Этот массив RAM хранит 16 значений, размер каждого из которых составляет один бит. Количество значений, хранящихся в массиве RAM, напрямую зависит от количества входов «Адрес». В отсутствие таких входов (как в случае с однобитной и 8-битной защелками) может быть сохранено только одно значение. При наличии одного входа «Адрес» можно сохранить два значения. Два входа «Адрес» позволяют хранить четыре значения, три входа «Адрес» — в​ осемь, четыре входа — ​шестнадцать.

Что произойдет, если вы отключите его от источника питания? Все электромагниты потеряют свои магнитные свойства, и с громким щелчком все контакты реле вернутся в свое исходное положение. А содержимое этой памяти? Оно исчезнет навсегда! Вот почему память с произвольным доступом также называется энергозависимой. Для хранения содержимого ей требуется постоянное энергоснабжение. На смену реле пришли такие электронные устройства, как вакуумные лампы и транзисторы.

Глава 18. От счетов к микросхемам

Что такое перфокарта

Перфокарта — это плотная бумажная карточка, на которой данные кодируются наличием или отсутствием отверстий в заранее определённых позициях. Каждая позиция соответствует одному символу или единице информации (биту). Машины могли считывать эти отверстия с помощью механических, электрических или оптических датчиков, превращая их в данные для вычислений.

Идея и происхождение

Идея использования перфокарт возникла задолго до компьютеров:

  • 1801 год — Жозеф Мари Жаккард (французский изобретатель) создал ткацкий станок Жаккарда, который использовал перфокарты для автоматического управления узорами ткани. Отверстия в карте определяли, какие нити поднимать — фактически это была первая программа, закодированная на носителе.

  • 1830–1840-е — Чарльз Бэббидж (британский математик) вдохновился идеей Жаккарда и предложил использовать перфокарты для управления своей «аналитической машиной» — механическим прототипом компьютера.

  • Главный прорыв произошёл в 1890-х, когда Герман Холлерит адаптировал идею перфокарт для обработки статистических данных. Он создал табулирующую машину, которая:

    • использовала перфокарты размером с долларовую банкноту;
    • кодировала на них информацию о людях (возраст, пол, место жительства и т. д.);
    • считывала отверстия электромеханическим способом.

    Благодаря этому изобретению перепись населения США 1890 года была завершена в шесть раз быстрее, чем предыдущая. Эта технология легла в основу бизнеса Холлерита — Tabulating Machine Company, ставшей ядром будущей IBM.

Перфокарты в XX веке

  • С 1920-х по 1970-е годы перфокарты были основным средством ввода, хранения и вывода данных в вычислительных системах.
  • Они применялись в бухгалтерии, на фабриках, в банках, на ранних компьютерах и даже в космических программах NASA.
  • Стандартная карта IBM имела 80 колонок и 12 строк — отсюда пошёл формат «80 символов в строке» в старых текстовых интерфейсах.
    • Лента с перфорацией содержала код программы

Уход в прошлое

С развитием магнитных носителей (ленты, диски) в 1970–1980-х годах перфокарты постепенно ушли из употребления. Но они оставили заметный след — даже современные языки программирования, вроде Fortran или COBOL, изначально проектировались под ограничения 80-колоночной карты.

Компьютер «Марк II» был самой крупной релейной машиной, использующей 13 тысяч реле. Реле подходили для создания компьютеров, но были неидеальны. Поскольку они были механическими, их работа основывалась на изгибании металлической пластины. После продолжительной работы реле могли сломаться, а также выйти из строя из-за частичек грязи или бумаги, застрявших между контактами. Известен случай, когда в 1947 году из реле компьютера «Марк II» в Гарварде была извлечена мошка. Грейс Хоппер (1906–1992), сотрудничавшая с Эйкеном с 1944 года, а позднее ставшая известным специалистом в области языков программирования, приклеила эту мошку в журнал с пометкой: «Первый отловленный баг».

Возможная замена для реле — ​вакуумная лампа, разработанная Джоном Флемингом (1849–1945) и Ли де Форестом (1873–1961) для радио. К началу 1940-х годов вакуумные лампы повсеместно использовались для усиления телефонных сигналов. Большое преимущество использования вакуумных ламп по сравнению с реле в том, что лампы могут переключаться из одного состояния в другое примерно за одну миллионную долю секунды — ​за одну микросекунду. Практически в каждом доме был радиоприемник, наполненный светящимися трубками, которые усиливали радиосигналы, обеспечивая их слышимость. Как и в случае с реле, из вакуумных ламп можно собрать вентили И, ИЛИ, И-НЕ и ИЛИ-НЕ. Не важно, из чего собраны вентили — ​из реле или из вакуумных ламп. Вентили всегда можно объединить в сумматоры, селекторы, дешифраторы, триггеры и счетчики. Тем не менее у вакуумных ламп наблюдались свои недостатки: они были дорогими, потребляли много электричества и выделяли много тепла. Самая большая проблема заключалась в том, что лампы в итоге перегорали. Кроме того, в компьютере используется так много вакуумных ламп, что они могут перегорать в среднем каждые несколько минут.

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

Во времена компьютера EDVAC (1940-х) было нецелесообразно создавать из вакуумных ламп память большого объема. Вместо этого было предложено несколько весьма странных решений. Среди успешных было использование памяти с ртутной линией задержки, в которой применялись пятифутовые трубки с ртутью. С одного конца трубки с интервалом около одной микросекунды в ртуть посылались слабые импульсы. За одну миллисекунду они достигали другого конца трубки, где детектировались как звуковые волны и отправлялись обратно. Таким образом, каждая трубка с ртутью могла хранить около 1024 бит информации. Только в середине 1950-х годов была разработана память, состоявшая из больших массивов маленьких намагниченных металлических колец, через которые проходили провода. Каждое такое кольцо могло хранить один бит информации.

Разработка транзистора (1947 год) ознаменовала начало эры твердотельной электроники, которая называется так потому, что транзисторы не требуют использования вакуумных ламп и создаются из твердых веществ, в частности из полупроводников, чаще всего из кремния (в наши дни). Помимо того, что по сравнению с вакуумными лампами транзисторы имеют куда меньшие размеры, они потребляют гораздо меньше электроэнергии, генерируют меньше тепла и дольше служат. Носить в кармане ламповый радиоприемник было невозможно. Транзисторный радиоприемник может питаться от небольшой батарейки и, в отличие от лампового, не нагревается.

Интегральная микросхема - на одном кристалле кремния можно объединить несколько транзисторов, а также резисторы и другие электрические компоненты. Чаще всего интегральные микросхемы помещаются в прямоугольный пластиковый корпус DIP (dual inline package, корпус с двухрядным расположением штырьковых выводов) с 14, 16 или даже 40 выводами.

Аббревиатура ТТЛ расшифровывается как транзисторно-транзисторная логика. Каждая интегральная схема серии 7400 состоит из логических вентилей, сконфигурированных определенным образом. Некоторые микросхемы — прос­тые логические вентили, из которых можно создать более крупные компоненты; другие — г​ отовые компоненты: триггеры, сумматоры, селекторы и дешифраторы.

Соединить между собой микросхемы намного проще, чем отдельные транзисторы. Однако вы вряд ли бы захотели использовать схемы ТТЛ для создания массива RAM объемом 64 килобайт.

Для создания массива RAM объемом 64 килобайт вам понадобилось бы 2048 TTL чипов размера 256x1! Если бы компьютер, описанный в главе 17, был собран из микросхем ТТЛ, он бы нормально работал с тактовой частотой десять мегагерц. На выполнение каждой инструкции уходило бы 400 наносекунд. Это, безусловно, многократно превышает скорость работы релейных устройств. Микросхемы ТТЛ никогда не были оптимальной технологией для создания памяти.

Другим популярным семейством чипов является КМОП (комплементарная структура металл — оксид — полупроводник), или CMOS (complementary metal-oxide-semiconductor).

Потребляемая мощность микросхем ТТЛ — о​ т 4,75 до 5,25 вольта, для микро­схем КМОП — о​ т 3 до 18 вольт. Довольно большой диапазон! Кроме того, микросхемы КМОП потребляют гораздо меньше энергии по сравнению с ТТЛ-чипами, что делает возможным создание на их основе небольших устройств, работающих от батареек. Недостаток микросхемы КМОП — ​низкая скорость работы. Например, гарантированное время установки 4-битного полного сумматора КМОП 4008, работающего от напряжения 5 вольт, — 750 наносекунд. Скорость увеличивается по мере роста напряжения и составляет 250 наносекунд при десяти вольтах и 190 наносекунд — ​при 15 вольтах. Однако по этому показателю устройство на основе микросхем КМОП сильно отстает от 4-битного ТТЛ-сумматора, время установки которого 24 наносекунды.

Сегодня существуют версии ТТЛ-чипов с малым энергопотреблением и высокоскоростные версии микросхем КМОП.

К началу 1970-х стало возможным использовать ИС для сборки компьютерного процессора целиком на единой плате. А размещение всего процессора в одном чипе было лишь вопросом времени. Первым важным продуктом компании Intel в 1970 году стал чип памяти с наибольшей на тот момент емкостью 1024 бит. Как отметил инженер Intel Тед Хофф, «вместо калькулятора с возможностью программирования я хотел создать компьютер общего назначения, запрограммированный на выполнение функций калькулятора». Это привело к разработке Intel 4004, первого «компьютера в чипе», или микропроцессора. Продажи микросхемы 4004 начались в ноябре 1971 года, она содержала 2300 транзисторов. (Согласно закону Мура, микропроцессоры, созданные 18 лет спустя, должны содержать примерно в 4000 раз больше транзисторов, или около десяти миллионов. Это предсказание оказалось довольно точным.)

Схема 4004 — ​это 4-разрядный микропроцессор, следовательно, ширина шины данных процессора составляла всего четыре бита. При сложении или вычитании чисел он был способен обрабатывать только четыре бита за один такт. Напротив, компьютер, разработанный в главе 17, имеет 8-разрядные шины данных и является 8-разрядным процессором. Как мы вскоре увидим, 8-разрядные микропроцессоры быстро превзошли 4-разрядные. Однако на этом никто не остановился. В конце 1970-х годов появились 16-разрядные микропроцессоры. Если вы вспомните компьютер из главы 17 и несколько команд, необходимых для сложения двух 16-разрядных чисел с помощью 8-разрядного процессора, то оцените преимущество 16-разрядного процессора. В середине 1980-х были разработаны 32-разрядные микропроцессоры, которые с тех пор остаются стандартом для домашних компьютеров.

Максимальная тактовая частота микросхемы 4004 составляла 108 тысяч циклов в секунду, или 108 килогерц. Тактовая частота — ​это максимальная скорость тактового генератора, который можно подключить к микропроцессору, чтобы заставить его работать. Более высокая скорость может привести к некорректной работе. К 1999 году тактовая частота микропроцессоров, предназначенных для домашних компьютеров, достигла отметки 500 мегагерц, что примерно в 5000 раз быстрее по сравнению с аналогичным показателем микросхемы 4004.

Объем адресуемой памяти микросхемы 4004 составлял 640 байт. Хотя это значение кажется смехотворно маленьким, оно соответствовало емкости микросхем памяти того времени учитывая, что оперативная память большинства домашних компьютеров не превышала 256 мегабайт.

Быстродействие — одна из самых важных причин, по которой мы вообще используем компьютеры. Максимальная тактовая частота оказывает очевидное влияние на общую скорость работы процессора, поскольку определяет быстроту выполнения каждой команды. Ширина шины данных процессора также влияет на его быстродействие. Несмотря на то что 4-разрядный процессор способен складывать 32-разрядные числа, при решении этой задачи он сильно уступает 32-разрядному. Тем не менее вы можете не сразу осознать, какое влияние на быстродействие оказывает максимальный объем адресуемой памяти процессора. По мере усложнения процессоров многие распространенные задачи, ранее выполнявшиеся программным обеспечением, включались в функционал самого процессора.

Глава 19. Два классических микропроцессора

Начало было скромным: первый микропроцессор Intel 4004 содержал около 2300 транзисторов. Сегодня количество транзисторов в микропроцессорах, предназначенных для домашних компьютеров, приблизилось к отметке в десять миллионов. Тем не менее принцип его работы на фундаментальном уровне остался прежним. Чип 8080 — ​это 8-разрядный микропроцессор, который содержит около 6000 транзисторов, работает с тактовой частотой два мегагерца и может адресовать 64 килобайта памяти. Микросхема 6800 содержит около 4000 транзисторов и тоже может адресовать 64 килобайта памяти.

Микросхемы 8080 и 6800 называются однокристальными микропроцессорами, или однокристальными компьютерами. Процессор — э​ то только один из компонентов компьютера. В дополнение к нему требуются по крайней мере некоторое оперативное запоминающее устройство (ОЗУ), какой-то способ, позволяющий пользователю записать информацию в компьютер (устройство ввода), а также извлечь ее из него (устройство вывода).

А сейчас рассмотрим сам микропроцессор. Часто его описание сопровож­ дается блок-схемой, которая иллюстрирует внутренние компоненты микропроцессора и то, как они связаны между собой. Однако на это мы уже насмотрелись в главе 17. Здесь мы постараемся понять, что происходит внутри процессора, наблюдая, как он взаимодействует с внешним миром. Другими словами, мы можем представить микропроцессор в качестве «черного ящика», внутреннюю работу которого нам не обязательно досконально изучать, чтобы разобраться в его функциях. Для этого достаточно ознакомиться с входными и выходными сигналами, в частности набором команд.

Чипы 8080 и 6800 — ​это интегральные микросхемы с 40 выводами. Чаще всего длина их корпуса составляет пять сантиметров, ширина — ​около 1,5 сантиметра, а высота — ​около трех миллиметров. Разумеется, мы говорим только о корпусе. Размер кремниевой пластины внутри намного меньше: в ранних версиях 8-разрядных микропроцессоров это квадрат со стороной около шести миллиметров. Корпус защищает кремниевый чип и обеспечивает доступ ко всем его входам и выходам.

Глава 23. Фиксированная точка, плавающая точка

У большинства современных компьютеров и программ, использующих числа с плавающей точкой, применяется стандарт, введенный в 1985 году Институтом инженеров электротехники и электроники (Institute of Electrical and Electronics Engineers, IEEE) и признанный Американским национальным институтом стандартов (American National Standards Institute, ANSI), — ANSI/IEEE Std 754–1985, с​ тандарт IEEE для двоичной арифметики с плавающей точкой.

Тип f64 — это 64-битное число с плавающей запятой, двлйной точности, размера 8 байт т.е. 64 бита (примерно 15–16 знаков точности) в формате IEEE 754:

  • 1 бит знак
  • 11 бит порядок (экспонента)
  • 52 бита мантисса (точнее — fraction, иногда её называют significand fraction)

Любое нормализованное f64 число хранится как:

Тип f32 — это 32-битное число с плавающей запятой, одинарной точности, размера 4 байта т.е. 32 бита (примерно 7 десятичных знаков точности, 8 уже предел) в формате IEEE 754:

  • 1 бит — знак (0 = положительное, 1 = отрицательное)
  • 8 бит — порядок (экспонента), масштабирует число
  • 23 бита — мантисса (точнее — fraction, иногда её называют significand fraction), хранит "значащие" биты ( ~7 значащих десятичных цифр точности всей мантиссы — до и после запятой вместе)

В нормализованных числах плавающей запятой есть скрытая единица в мантиссе. То есть реально мантисса хранит 24 бита точности, а не 23. Каждое число f32 может точно хранить все целые числа ДО 2²⁴, то есть до 16_777_216 (т.е. максимальное число на 8 знаков)

Так что любое дробное число вокруг 16_777_216_f32 уже не может быть представлено с шагом меньше 1, поэтому 16_777_216.123_f32 и 16_777_216.321_f32 округляются к одному и тому же 16_777_216.0 И дробные части вроде .123 и .321 просто не влезают в эти 24 бита, и всё округляется до ближайшего — 16_777_216.0

Любое нормализованное f32 число хранится как:

где:

  • bias (смещение экспоненты) = 127
  • exponent может быть от 1 до 254 (0 и 255 зарезервированы для спец. значений: 0, ∞, NaN)
  • fraction — это 23-битная дробь вида 0.xxxxxx

Например: константа f32::MAX :

fn main() {
    println!("{:032b}", f32::MAX.to_bits());  
    let n = format!("{:032b}", f32::MAX.to_bits());
    assert_eq!(n, "01111111011111111111111111111111".to_string());
}

Итого бинарный вид:

  • [sign (1 bit)][exponent (8 bit)][fraction bits (23 bit)]

  • [0] [11111110] [11111111111111111111111]

  • sign = 0

  • exponent=11111110=254, т.е. максимально возможная

    • битовое значение переводим в десятичное:
  • fraction (mantissa) = все 23 бита = 1 (максимально возможная дробная часть, = 11111111111111111111111 в двичном виде)

    • В нормализованных числах мантисса всегда равна 1.fraction, где fraction — это 23-битное двоичное дробное число.
    • Каждый бит в fraction представляет собой отрицательную степень двойки.
      • Первый (самый старший) бит fraction имеет
      • Второй бит имеет
      • 23-й (младший) бит имеет
    • значение 11111111111111111111111 означает двоичную дробь: (сумма геометрической прогрессии)
      • Двоичную дробь переводим в десятичную:
      • допустим у нас 23 битное значение 10...101 порядок бит [b22=1 b21=0 ... b2=1 b1=0 b0=1]
        • вес для b0 =
        • вес для b1 =
        • вес для b2 =
        • вес для b21 =
        • вес для b22 =
      • веса складываются с учетом бита:
      • для нашей дроби 11111111111111111111111 имеем это геометрическая прогрессия

Например: число 16_777_216_f32

Число одинарной точности с плавающей точкой имеет точность до одной части из 224 (одной части из 16_777_216, примерно до шести частей из 100 миллионов). Что это значит на самом деле? Во-первых, если вы попытаетесь выразить значения 16_777_216 и 16_777_217 в виде чисел одинарной точности с плавающей точкой, они окажутся одинаковыми. Более того, любое число в промежутке между этими двумя значениями (например, 16_777_216,5) тоже будет совпадать с ними.

Это также первое целое число, которое не может быть точно представлено в формате f32 при попытке записать 16_777_217.0, так как для этого потребовался бы ненулевой бит в дробной части, а экспонента уже находится на границе, за которой следует шаг квантования больше 1.

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

fn main() {
    assert_eq!(16_777_216.123_f32, 16_777_216.321_f32);// ERROR
    // 2^24=16,777216×10⁶=16_777_216
    println!("{:.10}", 16_777_216.123_f32);
    println!("{:.10}", 16_777_216.321_f32);
    // сравнивать с допуском (epsilon):
    // assert!((16_777_216.321_f64 - 16_777_216.123_f64).abs() < 1e-6);// 1e-6=1 × 10⁻⁶=0,000001
    println!("{:e}", f32::MAX);
    
   
    println!("{:032b}", 16_777_216_f32.to_bits()); // Выведет: 01001011100000000000000000000000
    let n = format!("{:032b}", 16_777_216_f32.to_bits());
    assert_eq!(n, "01001011100000000000000000000000".to_string());
}

Экспонента в IEEE-754 вычисляется на основе нормализованной двоичной формы числа.

Представим наше число в двоичном виде.

fn main() {
    println!("{:032b}", 16_777_216_f32.to_bits());  
    let n = format!("{:032b}", 16_777_216_f32.to_bits());
    assert_eq!(n, "01001011100000000000000000000000".to_string());
}

где:

  • значение 01001011100000000000000000000000 это:
    • [sign][exponent][fraction bits]
    • [0] [10010111] [00000000000000000000000]
  • sign (1 бит) = 0
  • exponent (8 бит) = 10010111 = 151
    • битовое значение переводим в десятичное:
  • fraction (mantissa) (23 бита) = 00000000000000000000000 = 0

Теперь применяем формулу:

Например: число 12345_f32

fn main() {
    println!("{:032b}", 12345_f32.to_bits());  
    let n = format!("{:032b}", 12345_f32.to_bits());
    assert_eq!(n, "01000110010000001110010000000000".to_string());
}

где:

  • значение 01000110010000001110010000000000 это:
    • [sign][exponent][fraction bits]
    • [0] [10001100] [10000001110010000000000]
  • sign (1 бит) = 0
  • exponent (8 бит) = 10001100 = 140
    • битовое значение переводим в десятичное:
  • fraction (mantissa) (23 бита) = 10000001110010000000000 = 0,5069580078125

Теперь применяем формулу:

Диапазон ≠ точность. Чем больше число по величине — тем меньше дробной точности у него остаётся

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

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

У float32 ~7 значащих десятичных цифр точности (всей мантиссы — до и после запятой вместе).

Но распределяются они так: чем больше целая часть, тем меньше знаков после запятой можно представить точно.

Таблица для максимального количества точных десятичных знаков после запятой в float32:

Число (N)Цифр в целой частиТочных знаков после запятойПример точного значенияПример потери точности
1161.1234561.1234567
102510.1234510.123456
10034100.1234100.12345
1_000431000.1231000.1234
10_0005210000.1210000.123
100_00061100000.1100000.12
1_000_000701000000.01000000.1
10_000_0008010000000.010000000.1
16_777_2168016777216.016777216.1

Пояснение:

  • Точных знаков после запятой = 7 − цифр_в_целой_части
  • Если результат отрицательный → 0 знаков после запятой точно
  • Числа ≥ 10 миллионов уже не могут хранить десятые доли точно
  • 16_777_216 — первое целое число, где float32 не может представить следующее целое число 16_777_217 (шаг = 2)

Глава 24. Языки высокого и низкого уровня

Иногда люди спорят, является ли программирование искусством или наукой. С одной стороны, существуют учебные программы в области компьютерных наук, а с другой — ​есть такие знаменитые книги, как «Искусство программирования» Дональда Кнута. Как писал физик Ричард Фейнман: «Информатика скорее подобна инженерному делу: нужно просто заставить что-то делать что-то». Если попросите 100 разных людей написать программу, которая выводит на экран простые числа, получите 100 различных решений. Даже те программисты, которые используют алгоритм Эратосфена, реализуют его не так, как это сделал я. Если бы программирование действительно было наукой, не существовало бы такого множества возможных решений, а неправильные заключения были бы более очевидными. Иногда стоящая перед программистом проб­лема вызывает у него озарения и творческие вспышки: в этом и заключается «искусство». И все же программирование в основном сводится к проектированию и строительству, подобно процессу возведения моста.

На домашних компьютерах язык БЕЙСИК начал использоваться в 1975 году, когда два приятеля, Билл Гейтс (род. 1955) и Пол Аллен (род. 1953), написали интерпретатор языка БЕЙСИК для микрокомпьютера Altair 8800 и основали корпорацию Microsoft.

Ранее я упоминал, что бóльшая часть языков высокого уровня не предполагает операций побитового сдвига или булевых операций над битами, которые являются частью функционала многих процессоров. Язык C — ​исключение из правила. Кроме того, важная особенность этого языка — ​поддержка указателей, которые, по сути, являются числовыми представлениями адресов памяти. Поскольку язык C поддерживает операции, реализующие многие инструкции процессора, он иногда классифицируется как язык ассемблера высокого уровня. По сравнению с любым другим языком типа АЛГОЛ язык C точнее всего имитирует общие наборы команд процессора.