КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 706312 томов
Объем библиотеки - 1349 Гб.
Всего авторов - 272773
Пользователей - 124661

Последние комментарии

Новое на форуме

Новое в блогах

Впечатления

DXBCKT про Калюжный: Страна Тюрягия (Публицистика)

Лет 10 назад, случайно увидев у кого-то на полке данную книгу — прочел не отрываясь... Сейчас же (по дикому стечению обстоятельств) эта книга вновь очутилась у меня в руках... С одной стороны — я не особо много помню, из прошлого прочтения (кроме единственного ощущения что «там» оказывается еще хреновей, чем я предполагал в своих худших размышлениях), с другой — книга порой так сильно перегружена цифрами (статистикой, нормативами,

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).
DXBCKT про Миронов: Много шума из никогда (Альтернативная история)

Имел тут глупость (впрочем как и прежде) купить том — не уточнив сперва его хронологию... В итоге же (кто бы сомневался) это оказалась естественно ВТОРАЯ часть данного цикла (а первой «в наличии нет и даже не планировалось»). Первую часть я честно пытался купить, но после долгих и безуспешных поисков недостающего - все же «плюнул» и решил прочесть ее «не на бумаге». В конце концов, так ли уж важен носитель, ведь главное - что бы «содержание

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).
DXBCKT про Москаленко: Малой. Книга 2 (Космическая фантастика)

Часть вторая (как и первая) так же была прослушана в формате аудио-версии буквально «влет»... Продолжение сюжета на сей раз открывает нам новую «локацию» (поселок). Здесь наш ГГ после «недолгих раздумий» и останется «куковать» в качестве младшего помошника подносчика запчастей))

Нет конечно, и здесь есть место «поиску хабара» на свалке и заумным диалогам (ворчливых стариков), и битвой с «контролерской мышью» (и всей крысиной шоблой

  подробнее ...

Рейтинг: 0 ( 0 за, 0 против).
iv4f3dorov про Соловьёв: Барин 2 (Альтернативная история)

Какая то бредятина. Писал "искусственный интеллект" - жертва перестройки, болонского процесса, ЕГЭ.

Рейтинг: 0 ( 0 за, 0 против).
iv4f3dorov про Соловьёв: Барин (Попаданцы)

Какая то бредятина. Писал "искусственный интеллект" - жертва перестройки, болонского процесса, ЕГЭ.

Рейтинг: +1 ( 1 за, 0 против).

Простая математика для простых программистов [Рик Гаско] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Серия

«Программирование»

Р и к Гаско

Простая
Математика
для
Простых
Программистов

Москва
СОЛОН­Пресс
2018

УДК 681.3
ББК 32.973­18
К 63
Под редакцией Н. Комлева
Рик Гаско
Простая Математика для Простых Программистов.
СОЛОН­Пресс, 2018. — 260 с : ил. (Серия «Программирование»)

— M.:

ISBN 978­5­91359­283­5
Книга простая математика в первую очередь для программистов, но не
только.
Для программистов в книге дано самое необходимое, то, без чего
программисту будет трудно. Материал представлен в самом простом и
правильном для немедленного употребления программистом изложении.
Настоящее применение математики программистом начинается тогда,
когда он, имея перед собой задачу, сам, без ансамбля, догадывается, какую
именно отрасль математики надо для такого случая вспомнить, какой
конкретно метод из неё взять и как его запрограммировать. И всё это сам, без
посторонней помощи.
Цель книги не в том, чтобы научить программировать математические
алгоритмы. Цель в том, чтобы взять задачу и понять какой математический
алгоритм к ней применить.
Прочитайте и применяйте.
Для не программиста эта книга ­ короткое введение в основы высшей
математики.
Особое внимание уделено базовым понятиям теории вероятностей.
Straight Math for Straight Programmers. — Moscow: SOLON­Press, 2018. —
260 p. (The "Programming" series)
По вопросам приобретения обращаться:
ООО «СОЛОН­Пресс»
Тел: (495) 617­39­64, (495) 617­39­65
E­mail: kniga@solon­press.ru, www.solon­press.ru
ISBN 978­5­91359­283­5

© «СОЛОН­Пресс», 2018
© Комлев Н. Ю., 2018

Посвящается
тем непростым программистам,
которые всё­таки знают математику

Содержание
Вступление
Здравствуйте, простые программисты!
О чём эта книга?
Как устроена эта книга?
На каком языке мы будем программировать?
Что почитать?
Что почитать. Особый случай. Кнут
Замеченные очепятки и ашипки. Короче, работа над ошибками
Ложка дёгтя в моей бочке варенья

7
7
8
10
11
11
13
17
19

Глава 1 Просто цифры.
А также системы счисления и кодировки
Зачем что­то ещё читать о цифрах?
Теория номер раз
Простой пример
Как бы это всё упростить?
Однако! Где же ошибка?
Как переводить из одной системы в другую
Простой пример в общем случае
Всё остальное и выводы
А что такое текстовый файл?
Общие, но очень полезные замечания

22
22
23
26
31
35
38
43
47
50
52

Глава 2 Простая арифметика
Зачем нам, таким умным и в белом пальто, простая арифметика?
А кто победит ­ арифметика или алгебра?
Красивые, но бесполезные концепции
Натуральные дроби ­ несложное упражнение
Очень длинные числа

54
54
55
56
59
70

Глава 3 Математическая логика. А надо?
Пожалуй, всё­таки надо
Вступление в логику
Исчисление предикатов, алгебра высказываний и прочее.
Звучит страшно, а так нет
Множества. В астрале и в реале
Сделать что­нибудь полезное
Разрозненные логические замечания

4

72
72
75
79
86
89

Глава 4 Комбинаторика и... И всё
Перестановки. Туда­сюда обратно, тебе и мне приятно
Размещение, переходящее в сочетание
Композиция и разбиение
Глава 5 Теория вероятностей.
Очень полезная на самом деле вещь
Апология (слово красивое)
В целом. Начало. Предельные теоремы
В целом. Продолжение. Основные теоремы
В целом. Окончание. Байес и компания
Глава 6 Теория вероятностей.
Некоторые бесполезные применения
Несколько простых упражнений. Ельцин, Титаник,
Луна и мёртвые президенты
Те самые президенты
Интерлюдия или Coitus Interruptus
Моделирование, выводы и поиск виноватых
Ещё упражнения. Болконский, Потрошитель и все­все­все
Теория вероятностей и азартные игры
Фараон или Штос
Кости
Рулетка
Очко
Покер
Подкидной дурак

93
93
94
97
98
98
101
106
109
111
111
114
117
123
129
134
134
137
145
154
156
159

Глава 7 Теория вероятностей. Скучные и важные понятия в виде
163
конспекта
Пояснение
163
Распределение. Что это такое
163
Ещё теория. Несколько несложных, но скучных формул.
Поговорим о среднем
164
Очень коротко, потому что всё ясно То же самое, вид сбоку
и немного сложнее
168
Великий и ужасный метод Монте­Карло
172
Немного о разном вероятностном
172
Глава 8 Настоящая Математика — математический анализ и что
с ним делать
176
Пределы
177
5

Ряды в теории
Ряды в живой природе
Дифференцирование
Производные и программирование
Ряды. Откуда всё­таки они берутся
Неопределённые интегралы
Определённые интегралы
Определённые интегралы. Что делают математики
Определённые интегралы. А программисты делают так
Определённые интегралы. Не только это

183
193
197
201
209
212
214
218
221
226

Приложение A Простая процедура для рисования графиков
с уместными комментариями. Длинное приложение,
но полезное. Но длинное
Интерфейс
Реализация
Демонстрация применения и перспективы развития

227
228
230
235

Приложение B Просто колода карт.
Полезна для простых опытов из теории вероятностей

238

Приложение C Та Самая Гравюра из Невского Альманаха
И бонус

246

Приложение D Баллада о синусе

248

Приложение E , печальное. Чего в книге нет, но могло быть
Матрицы и Высшая Алгебра
Графы и около
Теория игр
Вычислительная математика. Всё то же, вид сбоку.
Почему всё то же, и почему сбоку?
Аналитическая геометрия и немного человеческой
Теперь аналитическая геометрия. Просто для программистов
Приложение F , радостное. Чем заняться на досуге, по главам
Просто вообще
Математическая логика. Задания из Учебника логики
для средней школы 1953­го года издания

251
252
252
253

6

253
254
257
258
258
259

Вступление
Здравствуйте, простые программисты!
В словах этих ничего обидного нет, я и сам, в сущности, простой
программист. Но только я ещё и математик, так написано в моём, а
возможно, и вашем, дипломе, хотя, скорее всего, нет. Да, может быть, у
вас в дипломе написано, что вы отучились на факультете прикладной
математики, да и то не факт. Но по специальности вы, наверное,
какой­нибудь
специалист
по
информатике,
информационной
безопасности, безопасной экономике или экономичному веб­дизайну.
Я где­то уже говорил, но я повторю, я упорный. В моё время прикладных
математиков очень плохо учили программированию и тому, что сейчас
называется Computer Science или IT. Количество учебных часов было
таким же как и сейчас, но на IT, как я сказал их выделялось много меньше,
что, конечно плохо. Это минус к советской системе образования. Плюс к
советской системе образования в том, что мы нигде не работали, кроме
каникул и второй половины пятого курса. В результате, волей неволей,
гораздо больше времени оставалось на собственно математику и, вообще,
учебный процесс.
Не то, чтобы этим временем я очень хорошо воспользовался:
В те дни, когда в садах Лицея
Я безмятежно расцветал,
Читал охотно Апулея,
А Цицерона не читал
© Наше Всё Александр Сергеевич
Цицерон, если что, это такой древнеримский общественный деятель,
депутат и вообще болтолог, во всех отношениях. Апулей наоборот,
писатель тоже древнеримский, но более непристойной направленности. В
советские времена Апулея издавали массовыми тиражами, потому что
иначе нельзя, классик всё­таки, но отдельные фразы были совсем
непристойны, их так и печатали, на древнеримском языке. Я латынь имею
в виду, если кто­то не понял шутку юмора. Я латынь тогда не знал, да и
сейчас не знаю.

7

Это лирическое отступление намекает на тот факт, что в рамках учебного
процесса я злостно пренебрегал функциональным анализом и
уравнениями математической физики. Мне нравилось программировать. Я
программировал программы, которые мне надо было программировать для
получения зачётов и сдачи курсовых. Поскольку этого мне было мало, я
программировал программы для девушек из нашей группы.

Так вот, я ­ математик, и не только потому, что у меня в дипломе написано
Математик орфографически правильно. Я математик потому, что думаю
как математик, смотрю на мир как математик, рассуждаю как математик, и
это не лечится. Я очень плохой математик, я могу взять только простейший
интеграл и не могу решить даже простейшего дифференциального
уравнения, но я всё равно математик. Сам не похвалишь, никто не
похвалит.
Всё предыдущее было аргументированным объяснением того факта, что
эту книгу должен был написать именно я. И почему именно я напишу её
лучше всех.
Upd. В процессе работы над книгой выяснилось, что не так давно
окончившие прикладную математику коллеги катастрофически не
разбираются в предметах, которые нельзя сейчас и немедленно
запрограммировать. Например ­ теория вероятностей. Поэтому я не стал
углубляться в дебри ­ хотя, какие это дебри ­ и предпочёл несколько,
много раз повторить основы. Это не потому, что я ничего больше не знаю.
Это не я такой, это жизнь такая.
О чём эта книга?
Это очевидно из названия. Книга эта о математике для программистов.
Надо только уточнить, что такое простая математика и кто такие
простые программисты. Но сначала о том, как вообще связана
математика с программированием.
Бывает, что программисту выдают какой­то детально расписанный
математический алгоритм и требуют запрограммировать его, ни на шаг не
отступая от задания. Это скучно, неинтересно и, главное, никакого
отношения к математике не имеет. С тем же успехом программисту могли
8

бы выдать и детально расписанный алгоритм из какой­нибудь другой
области знания ­ бухгалтерии, астрономии, игры в очко. В большинстве
программ приходится хоть что­то считать. Должен заметить, что
программист, которому выдают детализированный алгоритм, обычно
получает не очень много денег ­ деньги у него отнимает тот, кто алгоритм
для него детализирует.
Бывает, что программисту выдают не вполне детализированное задание. К
примеру, там может быть требование «вычислить расстояние между
точками» или «найти минимум на интервале». Предполагается, что
программист понимает, о чём идёт речь, как минимум, и умеет это сделать,
как максимум. Такой программист ценится выше.
­ Граф стоит больше! © Собака на сене
Настоящее применение математики программистом начинается тогда,
когда он, имея перед собой задачу, сам, без ансамбля, догадывается, какую
именно отрасль математики надо для такого случая вспомнить, какой
конкретно метод из неё взять и как его запрограммировать. И всё это сам,
без посторонней помощи.
Основная проблема тривиальна, банальна и очевидна ­ если вы чего­то (из
математики) не знаете, то не сможете этого применить. Что хуже, вы даже
не догадаетесь, чего именно вы не знаете и не полезете искать это в
Интернете. Нет, всё ещё хуже ­ вы даже не догадаетесь, что вы чего­то не
знаете и вам надо что­то искать. Ещё раз и другими словами. Цель моя не в
том, чтобы научить программировать математические алгоритмы. Цель в
том, чтобы взять задачу и понять какой математический алгоритм к ней
применить.
Ещё бывает просто нужная математика, сама по себе, настолько нужная,
что адекватный программист даже её не замечает. Ведь никто не требует
от программиста знать таблицу умножения. Он просто должен знать её и
всё! Точно также программист должен уметь перевести из одной системы
счисления в другую (FF =255 ) и знать основы математической логики,
кажется, это называется исчисление предикатов ­ заменить условие
(((x>=0) and (y= 1 t h e n b e g i n
result:=1;
f o r k:=2 t o n do
r e s u l t : = r e s u l t * k;
end;
end;
{
begin

}

}

ChDir(ExtractFilePath(ParamStr(0)));
A s s i g n F i l e ( F, ' f a c t . f a c t ' ) ;
R e W r i t e ( F, 1 ) ;
numOf:=8;
B l o c k W r i t e ( F, numOf, 4 ) ;
f o r i : = 1 t o numOf do b e g i n
rec.arg:=i;
rec.fact:=Factorial(i);
B l o c k W r i t e ( F, r e c , S i z e O f ( r e c ) ) ;
end;
CloseFile(F);
end;

Комментарии.
Факториал
принципиально
не
запрограммирован
рекурсивно. Жутковатая строчка после основного begin делает каталог, из
которого запущена программа, текущим. То есть, именно туда и будет
записан наш файл. Оформите эту строку в виде процедуры, не пожалеете,
пригодится ещё много раз.
Итак, мы записали файл. Теперь сотрудник, который нас раздражает ­
праативный, ухаади! ­ его читает и выводит наши факториалы на экран,
вот так, текст в заголовке МП №1 расшифровывается как
Математическая Программа №1:

28

SEIKi
1/131072
2/393219
0/24
5/393216
720 / 330301447
0 / 40320
0 / 40320
0/40320

1 CIIMIIjj
После чего так называемый коллега выкатывает нам не бочку варенья, а
бочку претензий. Мы уверены, что у нас всё правильно, он уверен, что
правильно всё у него, а у нас наоборот. Каждый предлагает другому
посмотреть в его код и убедиться, насколько там всё гениально и даже
правильно. Но кто же будет смотреть в чужую программу, тут бы в своей
разобраться. В конце концов решают сойтись на относительно
нейтральной территории и заглянуть в файл. Что же там записалось?
Кстати, правильный программист немедленно заметит, что количество
факториалов совпадает с исходным, их ровно восемь.
Если вы очень серьёзный программист, для просмотра файла можно
использовать какой­нибудь серьёзный шестнадцатеричный редактор. Если
вы программист вроде меня, то можно применить FAR, или, того хуже,
Total Commander. Вне всякой зависимости от применённого спецсредства,
увидим мы вот такое:
00000000:

08

00

00

00

01

00

01

00

| 00

00

02

00

02

00

00

00

00000010:

03

00

06

00

00

00

04

00

| 18

00

00

00

05

00

78

00

00000020:

00

00

06

00

D0

02

00

00

| 07

00

B0

13

00

00

08

00

00000030:

80

9D

00

00

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

смещение 30? Хотя система счисления не указана, число это по основанию
шестнадцать. Понимать его надо в соответствии с приведённой ранее
формулой. 30 = 3*16 + 0*16 = 48. Здесь числа без указания системы
счисления уже десятичные. Если бы вы захотели сохранить в файл больше
значений, то где­то дальше встретили бы смещение 120, например. 120 =
1*16 + 4*16 + 0*16 = 320. Я так подробно объясняю такие тривиальные
вещи потому, что дальше будет хуже. Наши 40 и 120 ­ это нормальные
шестнадцатеричные числа. Впереди нас ждут ненормальные.
:

0

16

16

2

1

0

Почему 120 число нормальное? Потому что слева находятся самые
значимые/весомые цифры. Чем правее, тем меньше их вес, всё как в жизни,
всё как у традиционных десятичных чисел. Но эти смещения, вещь сугубо
вспомогательная, теперь перейдём к основной части.
16

Что мы пишем в файл в самом начале ­ правильно, количество записей,
оно равно восьми. В дампе видим 08 = 0*16 + 8*16 = 8. Всё сходится.
Немного дальше мы видим последовательность байт 04 00 18 00 00 00.
Четвёрка означает, что далее последует факториал от этого числа.
Напоминаю, 4! = 24. Проверяем. 18 = 1* 16 + 8*16 = 24. Опять всё
хорошо. Немного смущает тот факт, что все числа мы пишем как четыре
байта, а нужные значения оказались в самом первом, слева.
1

0

16

1

0

16

Проверим на чём­нибудь сложнее, на факториале восьми, он как раз в
конце файла. 8! = 40320. В файле у нас 80 9D 00 00. Можете сами
трудолюбиво перевести это в десятичное число, если результат не
понравится, повторите через калькулятор Windows (инженерный режим).
Вам всё равно не понравится. Почему? Ведь всё начиналось так хорошо.
Потому что мы имеем здесь непростую встречу суровой физической
правды и грациозной математической условности. В математике, в
арифметике, или кто там вообще отвечает за позиционные системы
счисления, условились, что старшие цифры находятся слева, а младшие ­
справа. В оперативной памяти младшие байты ­ слева, старшие справа.
Разумеется, в нашем контексте слова слева и справа синонимичны
словосочетаниям в начале и в конце. Это потому, что мы пишем слева
направо. У евреев и прочих семитов Ближнего Востока всё наоборот.
Дейкстра вспоминает, что его студент из Сирии всегда начинал перебор
массива с последнего, правого элемента. Как­то я три дня сражался с
настройками своего собственного монитора. Всех настроек было четыре
30

кнопки ­ реальных, не виртуальных. Я потерпел позорное поражение и
был вынужден прочесть руководство. Оказалось, настройку надо всегда
начинать с крайней правой кнопки, а я, понятное дело, сначала давил на
крайнюю левую.
При этом, каждый байт, имеющий, напоминаю, ровно 256 значений,
кодируется двумя цифрами по основанию 16. Эти цифры пишутся, как и
положено, от старшего (левого) разряда до младшего (правого). Какое всё
это имеет практическое значение, или, как нам правильно прочитать дамп?
Общее правило ­ если в дампе имеем последовательность байт, и эти
байты вместе представляют собой четырёхбайтовое целое, конкретно B 1
B2 B 3 B4, то результатом будет
B4*25 6 + B3*256 + B2*256 + B1*256
3

2

1

0

После чего значения каждого байта рассчитываются традиционным
арифметическим способом, как мы их и рассчитывали раньше и
подставляются в формулу. Исходная последовательность 8 0 9D 00 00.
Меняем порядок на 00 00 9D 80. Первые два байта нам неинтересны, далее
имеем:
256 * (9 *16 + D *16 ) + 256 * (8 *16 + 0 *16 ) = то, что надо.
1

1

16

0

16

0

1

16

0

16

Если вам это всё понятно и очевидно, то я рад за вас, но большинство
почему­то смущается и путается. Дальше в этой главе математической
теории не будет, только программистская практика.
Как бы это всё упростить?
Однако, мы отвлеклись от нашей увлекательной задачи ­ отыскания
виновного. В нашей части явной ошибки нет ­ файл мы пишем правильно.
Теперь пробежимся по нескольким смежным практическим вопросам.
Целые бывают не только четырёхбайтовые (integer). Также существуют, в
терминологии Delphi, shortint (один байт), smallint (два байта), int64
(восемь байт). Smallint мы тоже используем для хранения аргумента
функции, видимо из жадности ­ в целях экономии двух байтов. Внутри у
этих типов всё то же самое. Кроме того, есть ещё byte, word, и longword.
31

Это беззнаковые целые, что как бы намекает ­ ранее перечисленные типы
­ знаковые. Занудства ради, есть ещё longint и cardinal, но то, чем они
отличаются от integer и longword соответственно никакого отношения к
арифметике на имеет, а является вопросом сугубо политическим. Лучше
разберёмся со знаковыми и беззнаковыми ­ как их читать из дампа. Это
вопрос не политический, а всего лишь арифметический.
Сначала присмотримся в чём отличие этих переменных с точки зрения
программиста, на примере однобайтовых типов byte (беззнаковый) и
shortint (знаковый). Переменная типа byte вмещает в себя числа в
диапазоне от 0 до 255, shortint переваривает от ­127 до +128. Интуитивно
понятно, надо же где­то хранить информацию о знаке числа, а она у
нормальных людей занимает ровно один бит.
Кстати, если вы думаете, что в других языках программирования лучше, то
вы ошибаетесь. В других языках хуже, по крайней мере, во всех других
практически применяемых языках.
Хотя переменные без знака применяются гораздо реже, с ними всё просто.
С ними всё именно так, как я раньше и объяснял. Это со знаковыми хуже.
­ У тебя всё хорошо?
­ У меня всё хорошо. (Вешает трубку) — У тебя плохо.
© кино «Брат­2»
Проведём опыт. Объявим переменные, присвоим им значения, запишем в
файл и почитаем дамп.
var
: shortint;
: byte;

sh
by
sh:=127;
by:=127;
BlockWrite(
BlockWrite(

F, s h , 1 ) ;
F, b y , 1 ) ;

Поскольку число 127 попадает в диапазон обоих типов, результат
предсказуемо будет одинаковым:
0 0 0 0 0 0 0 0 : 7F 7F

32

Теперь запишем в файл числа 128, 255, ­ 1 , ­128. Для этого один из
операторов присваивания обязательно придётся закомментировать. Нельзя
переменной типа byte присвоить ­ 1 , а переменной типа shortint +128. В
результате получим вот такую табличку:
128
255
­1
­128

X
X
FF
80

80
FF
X
X

Таблица демонстрирует подозрительную симметричность, которая
объясняется использованием для кодирования отрицательных чисел так
называемого дополнительного
кода. Как записать в этом коде
отрицательное число, например ­1 (минус один)?
1.
2.
3.
4.

Взять число по модулю. Получаем 1.
Записать в двоичной системе. Получаем 0000 0001
Инвертировать. Получаем 1111 1110
Прибавить 1. Получаем 1111 1111

В результате имеем FF, которое, собственно, мы и так имеем. Но всё это
нам совсем не интересно, всё это делает за нас компьютер. Нам интересно
из дампа прочитать и понять отрицательно число. Легко. Всё то же самое,
но в обратном порядке. В дампе 80.
1.
2.
3.
4.
5.

Записать в двоичной системе. 1000 0000
Вычесть единицу. 0111 1111
Инвертировать. 1000 0000
Вернуть в десятичную. 128
Поменять знак. ­128

Подумайте, можно ли было обойтись без перевода в двоичную систему
счисления и остаться в родной шестнадцатеричной.
// Воспоминание, как я играл в игрушки
Не то, чтобы я сейчас совсем уже не играл в игрушки. Но ­ нет уже пороха
в пороховницах и ягод в ягодицах © Народное. Помните ли вы
Цивилизацию? Просто Цивилизацию, без номера. С тем же успехом
можно спросить, помните ли вы компьютеры на базе 286­го процессора и
33

DOS 5.0. Или это был DOS 3.3? На этом богатстве она, Цивилизация, и
работала.
Теперь цитата к месту из Википедии:
Неправильное использование беззнаковых целых может приводить к
неочевидным ошибкам из­за возникающего переполнения.
Мысль столь же бесспорная, сколь и очевидная. Не могу утверждать с
уверенностью, что у них там было внутри, знаковые или беззнаковые, и с
чем именно разработчики не справились, но симптомы были такие.
Последняя Цивилизация, в которую я играл, была третья. Там, как и в
первой, была возможность подкупить вражеский город засылкой шпиона с
деньгами. Осёл, гружённый золотом, возьмёт любую крепость © какой­то
Македонский, то ли Филипп, то ли Александр. В третьей Цивилизации у
меня это ни разу не получилось ­ денег банально не хватало.
В первой был шанс. Чем больше был город, тем больше была его
стоимость. Шпион время от времени подходил к городу и спрашивал, за
сколько они готовы продаться. Как только цена переваливала за 32787
случалось маленькое чудо. Цена города становилась отрицательной. То
есть, можно было получить и город даром и ещё денег в придачу. Я
понимаю, что это нечестно, но ведь все так делали.
// конец Воспоминания, как я играл в игрушки
// Угрызения совести
Да, я играю в игрушки до сих пор. Но, как сказал по аналогичному вопросу
­ курил ли он марихуану ­ президент Клинтон, курил, но не затягивался. И
я так же. Играю, но только в правильные игрушки. Civilization III, Panzer
General, Heroes of M M 3.5 (In WOG wee trust, вы меня конечно понимаете),
Fallout II и Morrowind. Иногда, впадая в маразм, дохожу до Railroad Tycoon
Deluxe.
Впрочем, обо всём этом, какие игрушки правильные, а какие нет, и как
правильные игрушки программировать, я пишу увлекательную книгу в
трех томах. Как только я её допишу, они её немедленно напечатают.
// Угрызения совести закончились

34

Однако! Где же ошибка?
Мы начали с поиска причины нечитаемости нашего файла, но куда­то
уклонились. Наш файл записан безусловно правильно. Поскольку чудес не
бывает, из этого следует, что он неправильно читается. Почему он
неправильно читается? Причин я могу придумать множество, это
нетрудно, придумывать причины, по которым программа не работает. Это
моя работа в конце концов. Возможно запись объявлена не так, как у нас,
например поля немного другие:
TFactRec = record
arg
fact
end;

: integer;
: smallint;

Этого достаточно, чтобы сокрушить программу. Возможно, файл
Открывается ЧуТЬ ПО Другому: R e W r i t e ( F, S i z e O f ( r e c ) ) ; Тоже НеПЛОХО.
Особо альтернативно одарённые обязательно спросят у автора первой
программы, каким именно способом он пишет в физический файл, и,
узнав, что через нетипизированный файл, принципиально будут читать
через файловый поток и, разумеется, неправильно. Такие сотрудники есть,
и в ассортименте. Однако коллега предъявляет текст и там мы видим, в
сокращении, вот такое:
type
TFactRec = r e c o r d
arg
fact
end;
A s s i g n F i l e ( F,
R e S e t ( F, 1 ) ;
BlockRead(

: smallint;
: integer;

'fact');

F, numOf, 4 ) ;

stroka:='';
f o r i : = 1 t o numOf do b e g i n
B l o c k R e a d ( F, r e c , S i z e O f ( r e c ) ) ;
stroka:=stroka + IntToStr(rec.arg) + ' / ' +
I n t T o S t r ( r e c . f a c t ) + #13#10;
end;

Наблюдаем полную симметрию чтения с записью и отсутствие какого
либо криминала. Так где же собака порылась? Возможно, вы сочтёте
пример немного искусственным, а я возражу. Что ситуация абсолютно
35

реальная, я видел почти всё, а это я точно видел. К текстам программ
(исходному коду) претензий нет. Они одинаковые. К исполняемому коду
(то, что с расширением .EXE) претензии есть. Они разные. Почему?
Идём на страницу Project\Options\Compiler.
записывающего, она выглядит так:

У

первого

автора,

А у второго, читающего, вот так:

Directories/Conditionals
Forms I Application

1
Versionlnfo
Packages
Compiler | CompiIerMessages | Linker
Runtime errors
!• Range checking
[• |/0 checking
K? Overflowchecking
Debugging
|7 Debug information
I? Local symbols
17 Reference info

Что это, собственно, означает? Напомню, как объявлена запись, которую
мы с вами пишем в файл:
36

type
TFactRec = r e c o r d
arg
fact
end;

: smallint;
: integer;

Обратите внимание, первое поле имеет размер 2 байта, второе ­ 4 байта. У
первого программиста, который пишет, так всё и будет записано: /2 байта
arg/4 байта fact/. Казалось бы, а как иначе? Однако, можно и иначе.
Волшебное число 4 в настройках читающего программиста означает, что
он предполагает все поля записи выровненными по адресам с кратностью
4. То есть, первый программист имеет для поля arg адрес ­ условно ­
00000000, для поля fact ­ адрес 00000002, а если бы в записи были ещё
поля, то третье поле имело бы адрес 00000006. Для второго программиста
эти адреса будут, соответственно, 00000000, 00000004, 00000008. А что
будет в двух неизвестно откуда взявшихся байтах? Нули, естественно. В
нашем конкретном случае ­ /2 байта arg/2 нулевых байта/4 байта fact/.
Что было бы, если бы первый программист действовал в соответствии с
ожиданиями второго, и установил бы те же настройки? Что оказалось бы в
файле? Правильно, вот это:
00000000
00000010
00000020
00000030
00000040

08
02
18
D0
30

00
00
00
02
9D

00
00
00
00
00

00
00
00
00
00

01
03
05
07

00
00
00
00

00
00
00
00

00
00
00
00

|
|
|
|

01
06
78
B0

00
00
00
13

00
00
00
00

00
00
00
00

02
04
06
08

00
00
00
00

00
00
00
00

00
00
00
00

И именно вот это второй программист и пытается прочитать, с немного
предсказуемым результатом. Обрате внимание, пока мы не пишем данные
в файл, никакого значения эти настройки не имеют, мы обращаемся к
полям записи по имени, а транслятор сам вычисляет смещения. Хотя...
// воспоминания о коллеге
Коллега принципиально обращался к полям записи по смещениям
относительно начала записи. Мотивировал это он тем, что пишет на C++.
Специально для него и других мы написали утилиту, которая
конвертировала объявления констант, типов и переменных из Паскаля в
C++, но коллега был упорен и на провокации не поддавался. Более того, он
три раза ходил к руководству и, ссылаясь на невыносимые условия работы,
требовал повысить заработную плату ­ а иначе уволится. Два раза
сработало. Сейчас он трудится в другой, суровой конторе, с жёсткой
37

дисциплиной. Только теперь он не программист, а, как это по­русски ­
системный аналитик, то есть пишет спецификации и задания для рядовых
кодеров. Ему очень нравится.
В чём мораль? Морали нет.
// конец Воспоминания о коллеге
Пример может показаться громоздким и искусственным. Громоздким ­ да,
искусственным ­ ни разу, совершенно обычная, штатная ситуация. Если
вы надеетесь, что в C++ лучше, то вы наивная чукотская девушка ­ там
абсолютна та же фигня.
Как это лечится? Можно лечить дисциплинарными взысканиями ­ порка и
карцер ­ с тем, чтобы все­все­все выставили в настройках одни и те же
циферки. Но лучше слегка изменить объявление записи, вот так:
type
TFactRec = packed
arg
fact
end;

record
: smallint;
: integer;

Волшебное слово packed равнозначно полному отсутствию выравнивания,
то есть цифре 1 в настройках. Всё пишется как есть, без затей. Кстати, в
C++ всё то же самое, только слово другое, найдите сами.
Вам такое программирование не нравится? Извините, миром правят
Рептилоиды. И не только миром. И не только они. Тему Рептилоидов я
рассмотрю в главе о теории вероятностей, в разделе о теориях заговора.
Как переводить из одной системы в другую
Как видно из предыдущих разделов. переводить из одной системы
счисления в другую приходится. Как показывает мой опыт, переводить
приходится часто. А как?
Можно запустить казённый калькулятор из Windows. Легко, доступно,
хотя есть недостатки. Только четыре системы счисления, по основаниям
2,8,10,16. На практике больше и не надо. Второй недостаток серьёзнее. Для
разборки дампа это подойдёт, хотя, если вы взялись разбирать дамп,
шестнадцатеричные числа давно пора уже читать в уме. Другой
недостаток ­ иногда возникает необходимость перевода в другую систему
38

счисления программным образом, то есть, переводите не вы в уме, а ваша
программа. Тут калькулятор не поможет.
Но пока немного о переводе без компьютера. Как переводить из любой
системы в нашу, родную, десятичную, должно быть ясно из ранее
приведённой формулы. Как перевести из десятичной в любую другую,
проиллюстрируем на конкретном частном примере. Переведём в
семеричную систему, просто потому. что о такой системе я никогда и не
слышал. Это будет интересно и неожиданно и для меня и для вас. За
исходное, в десятеричной системе, возьмём любимое число 255 .
10

255 делим нацело на 7. Результат 36, остаток 3.
36 делим нацело на 7. Результат 5, остаток 1.
5 делим нацело на 7. Результат 0, остаток 5.
Поскольку результат 0, на этом всё заканчиваем и собираем результат,
снизу вверх.
255 = 513
10

7

Проверка. 513 = 5*7 + 1*7 + 3* 7 = 245 + 7 + 4 = 255
2

1

0

7

А теперь несколько относительно простых и честных способов для
частных случаев.
Из двоичной в восьмеричную и обратно. Например, 11000011 перевести в
восьмеричную систему счисления. Разбиваем цифры на тройки, начиная
справа ­ 11/000/011. Каждую тройку, хоть слева, хоть справа, переводим в
десятичное число. Всё. 11000011 =303 . Обратное преобразование для 77 .
Каждую цифру преобразуем в двоичную тройку. 77 = 111111 .
2

2

8

8

8

2

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

B 16
D

1

'16'



10102
10112
11002
11012
11102

39

Fi6

= 11112

Дальше всё то же, что и с восьмеричной системой.
Перевод из восьмеричной в шестнадцатеричную и обратно несколько
сложнее. Как ни печально, самый простой способ это сначала перевести в
двоичную систему, а потом как обычно.
Но мы будучи всё­таки программистами хотели бы знать, как переводить
из системы в систему программно. Ещё раз напоминаю (или я этого ещё не
говорил?) ­ внутри компьютера никаких систем счисления нет, по крайне
мере в более­менее современных языках программирования. Раньше ­ да,
были. Поэтому говоря о переводе из одной системы в другую мы имеем в
виду перевод из символьного представления числа в одной системе в
числовое (без системы) или наоборот. Или из одной системы в символьном
представлении в другое символьное представление. Я долго над этим
думал, прежде чем сформулировать.
Для лучшего понимания этого смутного утверждения, посмотрим какие
функции для перевода есть в Delphi. Там нет почти ничего, кроме двух ­
HexToBin и BinToHex. Параметры и результаты их строковые. Точнее, эти
две функции там гипотетически есть. Если вам непонятно слово
гипотетически, посмотрите в гугле, только не самую первую ссылку, а
чуть пониже. От нас требуют передавать входной и выходной параметры
(забудьте, что это функция) как PChar ­ мы терпеливые. От нас требуют
заранее выделить память род результат. Мало того ­ от нас требуют писать
входное число маленькими буковками. Не гордое 'FF', а позорное ' f f .
Тварь ли я дрожащая, или право имею? © потомственный дворянин Родя
Раскольников
Вот и мы тоже, право имеем. Имеем право написать функцию перевода из
любой системы счисления в любую. Почти. Можно и в любую, но
фантазия наша ограничена набором символов, имеющихся в нашем
распоряжении. Что может шестнадцатеричная система? Правильно
шестнадцать символов, десять десятичных плюс шесть из английского
алфавита ­ A.B,C,D,E,F. Если мы суммируем наши исконные десять и не
наши, английские, двадцать шесть, то получим 36, что некрасиво. Красиво
будет 32 ­ десять цифр плюс английские буквы в диапазоне A..V.
40

Напишем функцию перевода числе в пределах систем счисления по
основанию не больше 32. Полезно, несложно и практично. Для краткости,
проверку на корректность входной строки выкинем. Разумеется, если вы
захотите использовать эту функцию в реальной, печальной, унылой жизни
— проверять придётся.
Прежде чем кодировать самую простую из простых программ, надо
понять, что мы от неё хотим ­ иначе говоря, сформулировать Техническое
Задание (ТЗ), Если мы пишем функцию, мы должны заранее знать, что
поступает ей на вход и что мы имеем на выходе. Если мы очень
аккуратные и правильные программисты, то мы заранее напишем набор
тестов для нашей функции. И не только входные данные, но и ожидаемый
от них результат. Но это так, к слову.
На выходе будет строка, представляющая наше число в новой системе, это
понятно. На входе тоже строка, в исходной системе счисления. И, мне
кажется, ещё два целых числа ­ основание входной системы счисления и
выходной. Если вы можете придумать что­то более изящное, тот
придумайте.
f u n c t i o n AnyToAny(

inStr
inBase, outBase

: string;
: integer)

: string;

— Если вас собьют, вы обязаны сжечь это письмо еще до того, как
успеете отстегнуть лямки парашюта.
— Я не смогу сжечь письмо до того, как отстегну лямки парашюта,
оттого что меня будет тащить по земле. Но первое, что я сделаю,
отстегнув лямки, так это сожгу письмо.
© Семнадцать мгновений весны
Первое, что вы должны сделать, задумав написать функцию ­ написать для
неё тестовую программу, причём ещё до того, как успеете написать саму
функцию. Ну ладно, вы не сможете написать тестовую программу до того,
как определите интерфейс функции. Но как только определите ­ так сразу.
Тестовая программа очень простая, но достаточно наглядная. Главное, что
мы видим и входные и выходные данные.
Первый тест будет уже проработанный руками, с семеричной системой.
Объявления переменных пропускаем.
41

inStr:='255';
// 10
inBase:=10;
outBase:=7;
outStr:=AnyToAny( i n S t r , inBase, outBase);
ShowMessage( I n t T o S t r ( i n B a s e ) + ' — > ' + I n t T o S t r ( o u t B a s e )
+ i n S t r + ' ==> ' + o u t S t r ) ;

+ #13#10

Сама функция. Само собой разумеется, что сначала надо проверить
входные данные на корректность. Если на вход можно подать что­то
кривое, то именно кривое и подадут. Проверку пропускаем, напишите
сами. Основания систем счисления должны лежать в диапазоне от 1 до 32,
и входная строка должна содержать только допустимые символы. А вот и
они:
const
numOf = 3 2 ;
syms
= '0123456789ABCDEFGHIJKLMNOPQRSTUV';

Объявления переменных тоже пропускаем, поверьте на слово, что все они
целые. Теперь нам надо осмыслить сложный философский и
методологический вопрос. Алгоритм перевода у нас всё тот же, с
делением. Но делить числа в произвольной кодировке мы не умеем. Мы бы
конечно смогли написать функцию, но зачем?
­ Вовочка, почему тебя вчера не было в школе?
­ Мы с папой водили корову к быку.
­ А что, папа сам бы не смог?
­ Папа, конечно, смог бы, но бык лучше © Старая шутка юмора
Это я к тому, что числа типа integer делятся как­то сами собой, без нашего
участия. Поэтому мы должны привести сначала наше в произвольной
системе счисления число к типу integer. Повторюсь, внутри в переменной
языка программирования системы счисления нет, но присвоить ей можно
только что­то десятичное или шестнадцатеричное. Это особенности
нашего языка программирования, проверьте, как с этим обстоят дела в
других. Следовательно, придётся сначала перевести наше число в
произвольной системе счисления в десятичное (придумайте шестнадцать
объяснений того, почему не в шестнадцатеричное).
Алгоритм перевода в десятичную систему счисления нам известен:
//

А здесь

могла

быть

Баша

проьерка

42

ьходных

данных!!!

dec:=0;
f o r i : = 1 t o L e n g t h ( i n S t r ) do b e g i n
o n e : = P o s ( i n S t r [ i ] , syms) ­ 1 ;
d e c : = d e c + Round( I n t P o w e r ( i n B a s e , L e n g t h ( i n S t r ) ­ i ) )
end;

* one;

А теперь легко и непринуждённо кодируем основную часть:
result:='';
rez:=dec;
repeat
o s t : = r e z mod o u t B a s e ;
rez:=rez d i v outBase;
result:=syms[ost+1] +
u n t i l r e z = 0;

result;

Комментарии. Нравится ли мне это код? Нет, не нравится мне этот код,
потому что если поменять местами два первых оператора внутри цикла,
код работает неправильно. Понятно, что нельзя менять местами строки
программы, но эти два оператора, если не присматриваться, выглядят
симметрично и невинно. Присмотритесь и обдумайте. И, разумеется,
протестируйте функцию не менее, чем на десяти тестах. Меня­то хватило
всего на четыре.
Простой пример в общем случае
В реальной жизни данные бывают не только целыми. Это, конечно, уже не
совсем математика, но около математики и для программиста очень
желательное знание. Как выглядит файл, в который записали не только
целые, а что­то ещё? А что ещё бывает? Бывают числа с плавающей
точкой. Они, как и целые, бывают разной длины, но, как и с целыми,
принцип записи остаётся неизменным. Бывают символьные данные. С
символьными данными просто зоопарк, в смысле их разнообразия.
Гуляют там животные невиданной красы © Слова не того, кого вы
думали, на музыку не того, кого вы думали
// С высунутым от гордости языком
А у меня та самая пресловутая пластинка есть. И даже агрегат есть, на
котором её играть.
// конец Высунутого языка
Так вот, о символьных данных. В Delphi есть просто символы ­ char. Есть
символьные массивы. Есть короткие строки, доставшиеся в наследство от
43

Турбо Паскаля. И есть длинные строки, почти объекты и почти
совместимые с C++. Эти типы совместимы между собой по хитрым
невнятным правилам, но нам это неинтересно, потому что здесь и сейчас
мы говорим о математике или почти о математике. Да и с числами с
плавающей точкой не всё так однозначно, я дальше поясню. Но обо всём
этом надо хоть что­то сказать, потому что в жизни пригодится. Всё
остальные типы ­ экзотика, о которой и упомянем в следующем разделе.
Случаи, они разные бывают © Поручик Ржевский
Далее ­ пример объявления переменных, их записи в файл и как они там
выглядят. Вот эти объявления и крошечная, но полезная процедура. По
соотношению, как принято сейчас формулировать «цена/качество»,
процедура не имеет себе равных. Она записывает в файл четыре нулевых
байта. В реальный файл, разумеется, такое писать на надо, но для наших
обучающих целей это резко повышает наглядность и читаемость файла,
разделяя его на легко различаемые сегменты.
var
file;
single;
char;
array[1..16]
string[7];
string;

F
fl
ch
aCh
shStr
longStr
{
p r o c e d u r e wZ;
var
fl
begin
fl:=0;
BlockWrite(
end;
{

:
F, f l ,

of char;

single;
4);

Теперь присвоение переменным хоть каких­то значений и запись в файл.
Короткие строки перед присвоением зачищаются нулевыми кодами ­
чтобы потом было лучше видно, сколько значащих символов в них
хранится.
fl:=0;
BlockWrite(
fl:=1;
BlockWrite(
wZ;
wZ;
ch:='C';
ch:='h';

F, f l ,
F, f l ,

BlockWrite(
BlockWrite(

4);
4);

F, c h , 1 ) ;
F, c h , 1 ) ;

44

ch:='a';
ch:='o';
wZ;
wZ;

BlockWrite(
BlockWrite(
wZ;

F, c h , 1 ) ;
F, c h , 1 ) ;

a C h : = ' H i x T O мене не любить
B l o c k W r i t e ( F, aCh, S i z e O f ( a C h ) ) ;

';

FillChar( shStr, SizeOf(shStr), #0);
shStr:='Hello';
B l o c k W r i t e ( F, s h S t r , S i z e O f ( s h S t r ) ) ;
FillChar( shStr, SizeOf(shStr), #0);
shStr:='Hello world!';
B l o c k W r i t e ( F, s h S t r , S i z e O f ( s h S t r ) ) ;

На немедленно возникающий правильный вопрос ­ а почему переменная
IongStr проигнорирована и не записана в файл, дадим немедленный
правильный ответ.
­ Подсудимый, садитесь.
­ Спасибо, я постою.
­ Гражданин судья, а он не может сесть! © Кавказская пленница
Мы не можем записать переменную типа string, потому что внутри она
простой указатель. Простой указатель вовсе не означает, что указатель сам
по себе очень простое понятие. Наш указатель указывает на совсем не
простой агрегат данных, но для нас сейчас это неважно. Записать это
нельзя. А если очень хочется ­ а хочется часто, то придётся перевести в
array of char подходящей длины и его уже записывать. Ещё обратите
внимание, что строку для того самого массива символов в исходном тексте
программы пришлось добивать пробелами ­ иначе транслятор не
пропускает.
Теперь поглядим, что у нас получилось на выходе. Поскольку мы записали
порядочное количество символьных данных, будет уместно привести и
правую колонку дампа, в который данные как раз и отображаются в
символьном виде. Даже те данные, для которых такое отображение совсем
не подходит и никакого смысла не несёт.
00000000
00000010
00000020
00000030
00000040

00
43
CD
E1
06

00
68
69
E8
48

00
61
F5
F2
65

00
6F
F2
FC
6C

00
00
EE
20
6C

00
00
20
20
6F

80
00
EC
20
00

3F
00
E5
20
00

00
00
ED
20
07

45

00
00
E5
20
48

00
00
20
20
65

00
00
ED
20
6C

00
00
E5
20
6C

00
00
20
20
6F

00
00
EB
20
20

00
00
FE
20
77

Для лучшей читаемости, символьная колонка отдельно. Мысленно склейте
в панораму. Вместо пробелов ­ знак подчёркивания, тоже для лучшей
читаемости.
??
Chao
H i x T O мене не лгс
бить
*Hello

•Hello

w

Интересное наблюдение ­ пробелы в символьной колонке не обязательно
соответствуют настоящим пробелам. Они могут заменять также
неотображаемые символы. Теперь по существу.
Первым в файл записано четырёхбайтовое с плавающей точкой, равное
нулю. В дампе мы видим соответственно четыре нулевых байта. Это не
может не радовать. К сожалению, далее нас ждёт немедленное
разочарование. Вместо единицы мы видим непонятно что. Числав
плавающем формате кодируются достаточно хитрым способом. Если вы
прочтёте, что вариантов кодировок ровно одна ­ IEEE 754, то это не совсем
так. Совсем не так, точнее. Другие кодировки мало распространены, но
зато присутствуют в таких важных отраслях науки и техники, что выучить
их придётся, если вы туда попадёте. Но это я отвлёкся. Мораль в том, что
чтение глазами плавающих чисел из файла доступно не всякому. Забудьте.
С символами интереснее и понятнее. Четыре подряд записанных
переменных типа char именно так и выглядят ­ четыре символа подряд, что
в шестнадцатеричном представлении, что в символьной колонке. Далее
следует символьный массив. С ним тоже всё прекрасно. А короткая строка,
то есть строка объявленная как string[n], где n константа, устроена
сложнее. Для нашей строки n=7. Общая длина переменной равна восьми,
потому что впереди находится байт, содержащий длину строки. Поскольку
это всего лишь байт, ясно что длина короткой строки не может превышать
255. К этому байту можно, но не рекомендуется, обраться как shStr[0].
Когда в первый раз мы присваиваем нашей строке строковую константу
длиной пять, последние два байта остаются, как и были нулевыми. Когда
мы присваиваем что­то изначально более длинное, чем длина короткой
строки, лишнее отбрасывается.

46

Настоящие, длинные строки (string) нельзя записать в файл просто так,
только через определённые не очень хитрые манипуляции. И прочитать
нельзя, без этих же манипуляций.
Всё остальное и выводы
О странном. Есть такой тип как boolean ­ булевский, логический. Если
вдуматься, а я вдумался, то зачем он вообще нужен? Вот стоило только
всерьёз задуматься и возник такой вопрос. Тем не менее, следующая глава,
или одна из следующих глав, будет посвящена математической логике, так
что без булевского типа никак не обойтись. Булевские типы бывают
просто булевские ­ boolean, и с подвывертом ­ ByteBool, WordBool,
LongBool. Про последние три немедленно забудьте, потому что даже я не
знаю, зачем их придумали. Единственно правильный тип boolean
интересует нас здесь только в отношение его вида в сохранённом файле.
Далее фрагменты программного кода:
bool
bool:=true;
bool:=false;

BlockWrite(
BlockWrite(

: boolean;
F, b o o l , S i z e O f ( b o o l ) ) ;
F, b o o l , S i z e O f ( b o o l ) ) ;

А вот результат:
00000000:

0 1 00

Вывод очевиден ­ булевская переменная занимает ровно один байт (не
бит!). True кодируется единицей, false ­ нулём.
Поскольку, если вы не забыли, далее будеет глава о математической
логике, нельзя обойтись и без множеств, в Delphi есть такой очень
экзотический тип ­ set.
О математическом смысле этих переменных, и о применении, весьма
ограниченном, мы поговорим позже, а сейчас только о сохранении в файл
и как это потом прочитать. Пусть у нас есть вот такое объявление и
инициализация множества (официально инициализация множества
почему­то называется вызовом его конструктора) :
const
lowS =
0;
highS = 255;
type
TSet255
= s e t o f lowS..highS;

47

var
a

: TSet255;

a: = [ 0 ] ;

Далее мы традиционным способом сохраняем в файл наше множество:
BlockWrite(

F, a, S i z e O f ( a ) ) ;

Чтобы два раза не вставать, запишем ещё и множество [0,2,4]. Что
получаем на выходе?
00000000:
00000010:

0 1 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 3F | 00 00 00 00 00 00 00 00

Первая строка для множества [0], вторая для множества [0,2,4]. Общий
объём файла составляет 32 байта и это не случайно. 32*8=256, а это
максимальная ёмкость множества в Delphi. Каждый элемент множества
кодируется тупо и в лоб ­ или он есть, или его нет ­ или единица в
соответствующем бите или ноль. Требует некоторых умственных усилий,
но вряд ли вам придётся с этим встретится в реальном программистском
мире.
И ещё ­ записать переменную типа file в файл нельзя. То есть, можно, но
бесполезно. Точно так же, как абсолютно бессмысленно записывать в файл
указатель или длинную строку. Обдумайте, можно ли записать в файл
объект (экземпляр класса). И если да, то что это значит?
Какой он, этот Слонопотам?
Любит ли он поросят или нет?
И как он их любит?.. © Милн в пересказе Заходера
Как пишутся в файл агрегаты данных ­ массивы, записи? Записи пишутся
и читаются без затей и без проблем, если вы не забудете объявить их как
packed record. С массивами чуть интересней. С первого взгляда всё
просто. Если у нас объявлен, проинициализирован и записан в файл массив
вот таким образом
a

:

array[1..4]

of integer;

a[1]:=1;
a[2]:=2;
a[3]:=3;
B l o c k W r i t e ( F, a, S i z e O f ( a ) ) ;

a[4]:=4;

48

то в файле мы увидим вот такое:
00000000:

01 00 00 00 02 00 00 00

| 03 00 00 00 04 00 00 00

Что, в общем­то, вполне ожидаемо ­ массив записывается в файл в
точности как если бы вместо него существовали несколько, по длине
массива, переменных того же типа. Всё становится более интригующим и
непредсказуемым, если массив двумерный, не говоря уже о больших
размерностях. Объясняю на примере.
a2

:

a2[1,1]:=11;
BlockWrite(

array[1..2,1..2]
a[1,2]:=12;

of integer;
a[2,1]:=21;

a[2,2]:=22;

F, a 2 , S i z e O f ( a 2 ) ) ;

На выходе получим:
0 0 0 0 0 0 0 0 : A1 00 00 00 A2 00 00 00

| 15 00 00 00 16 00 00 00

Это также не вызывает удивления, а зря. Говоря на программистском
диалекте, массивы в Delphi пишутся по строкам. В C++ тоже вроде бы. Я
спросил у Ясеня, тьфу, я спросили у ситника.
Ничего Петров не отвечает,
Только тихо ботами качает © Садистский стишок
Но это не всегда так. В Фортране массивы пишутся по столбцам, этот же
массив, записанный в файл программой на Фортране выглядел бы так:
0 0 0 0 0 0 0 0 : A1 00 00 00 15 00 00 00

| A2 00 00 00 16 00 00 00

Это касается не только тех, кто программирует на Фортране или читает
записанные программой на Фортране массивы. Это касается и тех, кому
придётся разбираться в фортрановской программе, и не думайте, что с
вами этого просто не может случиться. Может. В Фортране, как и в более
поздних языках, присутствует инициализация массивов при их объявлении
или как констант. Так вот, записывается она по столбцам, что, разумеется,
чисто визуально определить невозможно.
Это нельзя понять! Это надо запомнить! © из анекдота про грузинскую
школу.
49

И не говорите, что я вас не предупреждал. Обдумайте. Выясните, как с
этим в ЛИСПе. А кстати, как?
А что такое текстовый файл?
Сначала занудное уточнение, извините, если это для вас очевидно.
Текстовый файл (в программировании) не есть файл, в котором
содержится текст, то есть, файл который можно прочитать глазами с
помощью простой или сложной программы. Файлы Microsoft Word, его же
RTF, а также выползшие непонятно откуда fb2 и epub ни разу не являются
текстовыми с точки зрения программиста. Если подходить к вопросу
прагматически, текстовый файл ­ то, что можно открыть Блокнотом из
Windows. Формальное определение ­ в нём содержатся только текстовые
символы и символы форматирования. Со временем текстовые файлы
окончательно деградировали и выродились и их определение значительно
упростилось ­ текстовый файл есть то, в чём только буковки (и циферки) а
также символы возврата каретки (не спрашивайте, что это) ­ 13
десятеричное, и перевода строки ­ 10.
А теперь поглядим, как это выглядит. Вам это не понадобится никогда? Не
плюй в колодец, милый мой ­ А.С.Пушкин. Чтобы далеко не ходить за
примером, применим того же Александра Сергеевича из того же стиха,
откуда взято про колодец:
Пупок чернеет сквозь рубашку,
Наружу титька ­ милый вид!
Татьяна мнёт в руке бумажку,
Зане живот у ней болит.
Она затем поутру встала
При бледных месяца лучах
И на подтирку изорвала
Конечно Невский альманах.
//Филологический комментарий.
А.С.Пушкин, как знают все, написал роман в стихах Евгений Онегин.
Журнал, а точнее альманах, с названием, сюрприз, Невский Альманах,
напечатал иллюстрации к нему, на одной из которых изображена
Т.Д.Ларина за сочинением письма E.?.Онегину. К сожалению, вставить
50

картинку сюда я не могу. Соображения благопристойности, цензура, а
вдруг это будут читать дети?, технологические требования
издательства... В основном, конечно, технологические требования...
Кстати, были кем­то подкупленные филологи, утверждавшие, что Татьяне
в романе двенадцать лет! Сам Пушкин в письме к Вяземском заметил
мимоходом, что ей семнадцать. Филологи врут! Пушкин врёт! Они все
врут! Ей за сорок. Ладно, тогда женщины старели рано, ей за тридцать
пять. Посмотрите на картинку, вы найдёте, я уверен. Как теперь
обязательно надо предупредить, 14+.
// конец Филологического комментария
Upd. Я всё­таки не удержался, и впендюрил аутентичные гравюры в
приложение. Вас не раздражает слово аутентичные?
Как записать текст в файл? Тот способ, которым мы пользовались до этого
(через F : file) не подойдет. Можно использовать либо специально
предназначенную для этого разновидность файла, TextFile, либо класс
TStringList. Физический результат в виде файла будет одинаковым,
поэтому, не ища лёгких путей, сделаем это через файл.
var
TF

: TextFile;

begin
//
вы уже оформили это в виде
процедуры?
Cri Dir(ExtractFilePath(ParamStr(0)));
A s s i g n F i l e ( TF,
ReWrite(TF);

'SimpleTextFile');

W r i t e l n ( TF, 'Пупок чернеет сквозь р у б а ш к у ' ) ;
W r i t e l n ( TF, 'Наружу сиська ­ милый в и д ! ' ) ;
Writeln( TF); Writeln( TF);
Write(TF);
Write(TF);
CloseFile(TF);

Ограничимся двумя первыми строками, величие Пушкина это не умалит
(ударение на последнем слоге. Или на предпоследнем?). А зачем четыре
последних оператора записи в файл? А чтобы посмотреть, что из этого
получится. А получится вот что, символьная колонка отдельно:
0 0 0 0 0 0 0 0 : CF F3 EF EE EA 20 F7 E5 | F0 ED E5 E5 F2 20 F l EA
0 0 0 0 0 0 1 0 : E2 EE E7 FC 20 F0 F3 E l | E0 F8 EA F3 2C 0D 0A CD

51

0 0 0 0 0 0 2 0 : E0 F0 F3 E6 F3 20 F2 E8 | F2 FC EA E0 20 2D 20 EC
0 0 0 0 0 0 3 0 : E8 EB FB E9 20 E2 E8 E4 | 2 1 0D 0A 0D 0A 0D 0A
Пупок чернеет сквозь рубашку,
Наружу титька ­ милый вид!

Выводы. Символы пишутся как обычно, между строками имеем коды 0D
0A, в дельфийской нотации #13#10, на древнерусском наречии ­ BK ПС,
что в переводе означает «Возврат каретки» «Перевод строки». Согласно
древнейшим правилам, в конце текстового файла должен присутствовать
специальный символ со специальным, забытым даже Википедией, кодом ,
но его там никогда нет. Куда катится этот мир?
Что до наших экспериментальных операторов записи, оператор Writeln
записывает коды 0D 0E. При просмотре файла в текстовом виде это
проявляется как пустая строка. Оператор Write без объекта записи вообще
никакого эффекта ни на что не оказывает.
Общие, но очень полезные замечания
Почему это важно? Ведь далеко не каждый день приходится разбирать
бинарные файлы? Не каждый. Более того, вполне возможно, что лично вам
этим никогда не придётся заниматься, хотя это вряд ли. Но необходимость
разбора данных на уровне байтов возникает намного чаще, и, не только для
отладки через чтение файлов. Я буду здесь немного занудным, можно?
Первое ­ вам просто напросто могут вручить чужой файл и поставить
задачу ­ обеспечить гарантированное чтение этого файла в ваш
Программный Продукт. Ведь это всего­навсего ещё один формат. Если
повезёт, в комплекте с файлом вы получите описание в стиле штуки ­
целое, длина ­ плавающее, где что лежит — массив целых. В худшем случае
­ догадаетесь сами, во всех смыслах. А потом объясните своему
начальнику, что вы тут ни при чём.
Второе ­ всё вышесказанное применимо не только к файлам. Бывают ещё и
потоки всех видов (в смысле stream), которые могут сохраняться в файлы,
а могут не сохраняться, отчего нисколько не меняют своей сущности ­ это
те же файлы, только в памяти. Ну, или файлы ­ те же потоки, только на
диске. Бывают каналы (pipes), сокеты, разделяемая память (shared
memory), на которой, в сущности, всё предыдущее и строится. Внутри ­
всё то же самое, только отлаживать труднее.

52

Третье, не самое уважаемое занятие ­ стыковаться с программой на другом
языке, неважно как ­ через динамические библиотеки или через объектный
код. Труд этот не принесёт вам ни славы, ни восхищения, ни ромашек,
одна только ругань со всех сторон. Чтобы эту ругань минимизировать,
желательно очень хорошо знать как выглядят данные внутри хотя бы на
одном из двух стыкуемых языков.
И не забываем об отладке. Даже если ваша программа что­то тихо делает в
своём углу, никого не трогая, вам всё равно придётся отлаживаться. Если
ваша программа любым способом обменивается данными с любой другой
программой, закодированной кем­то другим, отлаживаться придётся не то,
чтобы много, а очень много. И вот тут­то всё это вы вспомните и заплачете,
что не выучили всего этого вовремя.
И всегда надо думать о большом и светлом. Напишите серьёзную, годную
процедуру (или программу). Если это программа, то на вход её поступает
бинарный файл, состоящий из записей одинаковой ­ обязательно ­
структуры. Если это процедура, то на вход её приходит неведомо откуда
взявшийся байтовый поток, содержащий то же самое. Возможно, записи
приходят по одной и в реальном времени.
И ещё раз ­ то, о чём я говорил в этой главе, это не совсем математика.
Сравните с правилом поджаривания трёх кусков хлеба на сковородке,
вмещающей только 2 куска: A B ; затем B C ; затем C ,A . Это не
дотягивает до математики. © Литлвуд, «Математическая смесь»
h

1

2

1

2

2

Это не совсем настоящая, большая математика. Но очень близко к ней. В
любом случае, всё это очень полезно для простого программиста.

53

Глава 2
Простая арифметика
Зачем нам, таким умным и в белом пальто, простая арифметика?
Поговорим об арифметике. Арифметика, в пределах этой главы, есть
искусство сложения, вычитания, умножения и ­ невероятно сложно ­
деления. Любой язык программирования предоставляет возможность
выполнения этих операция над целыми числами (деление только с
остатком) и над действительными числами. Но всем всегда хочется
большего.
Обычно в качестве примера нетривиальной арифметики предлагают
арифметику комплексных чисел (ударение в слове комплексных поставьте
по вкусу). Кроме того, это очень полезно и совершенно необходимо для
причастных к переменному току. Что удивительно, я не встречал ни
одного электрика, который бы слышал о комплексных числах. Однажды я
нашёл старый, ещё советский, учебник электротехники в двух томах.
Учебник был по­советски простой и доступный. На всякий случай ­ эта
обобщённая характеристика относится только к советским техническим
учебникам. Тем не менее, начиная с приблизительно девятой страницы
пошли сплошные комплексные числа. Поскольку, будучи прикладным
математиком, я был лишён прослушивания курса Функций Комплексного
Переменного, учебник отправился в то же место, где я его и нашёл (нет, не
то, что вы подумали).
// Размышление о тенденциях
Познакомился со студентом первого курса. То есть, я с ним был знаком и
раньше, но тогда он был школьником. Учится он не на менеджера (по
старому ­ приказчика), не на маркетолога (по старому ­ ярмарочного
зазывалу) и даже не на веб­дизайнера (подмалёвщика вывесок). Учится он,
опять­таки в переводе на старорусский язык, на Инженера Путей
Сообщения, то есть инженера­железнодорожника.
Юноша был очень огорчён, получив для изучения примерно тот же
учебник электротехники. Впрочем, огорчение его было несколько другого
характера ­ я в свой время огорчился, что от меня требуется знание ФКП,
он огорчился, что в наше время ещё надо изучать электротехнику.
// конец Размышлений

54

Впрочем, комплексные числа ­ это действительно очень полезная вещь, но
в качестве объекта тренировки программиста несколько заезженная.
Попробуем две другие темы ­ они тоже не новые, но, с одной стороны,
математически проще, с другой стороны ­ не менее полезны для
тренировки и постижения.
Первая тема несложна ­ арифметика натуральных дробей, впрочем, в той
же степени как несложна, она и не очень применительна. Для вычислений
она не очень нужна, а нужна она разве что для красивого представления
данных, да и то в редких случаях.
Тема номер два ­ арифметика длинных чисел. Это звучит красиво и
мелодично. Арифметические вычисления с высокой точностью ­ уже
как­то суховато.
Что скрывается за этим мы увидим в следующих разделах главы. Через
один ­ потому что следующий, очень короткий раздел будет посвящен
очень короткой, но возвышенной теории.
А кто победит — арифметика или алгебра?
Как нас учили в наше время в нашей школе, арифметика ­ это когда 2 + 3 =
5, алгебра это когда a + b = c. То есть арифметика оперирует с конкретными
числами, а алгебра с абстрактными. Для моего возраста это значит
следующее: пломбир, 19 копеек плюс пломбир, 19 копеек равняется 38
копеек. Это арифметика. А вот то, что пломбир плюс пломбир стоят как
два пломбира при любой их цене ­ это уже алгебра.
С возрастом и получением математического образования разница между
арифметикой и алгеброй стала сглаживаться ­ для меня, конечно.
Арифметика уплыла куда­то в страну серебристо­голубых негров, как
Гоблин Фродо в сказке Ролингс. Чуть позже туда же уехала и алгебра ­
зачем это всё прикладному математику?
Среди погрузившейся в никуда арифметической Атлантиды одинокими
утёсами торчат, или торчит (гусары, молчать!) кое­что из мною
прочитанного.
Д.Гильберт,

П.Бернайс
55

Основания математики.
арифметики.
М. «Наука», 1979
Grundlagen der Mathemaik

Логические

исчисления

и

формализация

Бесценную книгу эту я заполучил, как трофей, за лучший доклад (я не
шучу ­ лучший, там так и написано!), и я её прочитал! Даже! К сожалению,
для меня книга эта показалась слишком сложна и более того, вызвала
полное и законченное отвращение к попыткам объяснить очень научным
языком то, что и так очень просто ­ 2 + 2 = 4, ведь это просто? А зачем
целую книгу («700 стр.) это доказывать?
При дальнейшем изучении вопроса, точнее, дальнейшем изучении
терминологии, выяснилось, что высшая арифметика плавно переходит в
общую алгебру, которая, в свою очередь, превращается, как Золушка в
тыкву, в высшую алгебру. На данном этапе всё это словоблудие начинает
наполняться некоторым смыслом.
Красивые, но бесполезные концепции
Какие бывают числа? Для простейшего программиста ­ целые (integer) и
дробные (float). В некоторых, более извращённых языках (PL/I) были и
другие, но что ушло, то ушло. Далее в этом абзаце я говорю только о
программировании, а не о математике. Два целых числа можно сложить и
получить в результате целее число. С вычитанием абсолютно то же самое.
С умножением тоже неплохо. А вот с делением уже серьёзные проблемы.
Для целых чисел есть операция деления div, но вот такая загогулина
получается: 17 div 3 = 5, но 5x3 = 15, а не 17. Да, можно, конечно, включить
дополнительный интеллект мозга и сформулировать, что 17 = (17 div 3)х3
+ 17 mod 3 = 17. Это, безусловно, правильно, но получается, что одной
операции div у нас соответствуют две обратных ­ умножение и mod.
Что­то здесь не так.
Применяя числа с плавающей точкой, от этих размышлений мы
избавляемся. В результате деления single на single, имеем, опять­таки,
single. А теперь о том, как оно в математике?
Для начала в математике есть числа натуральные: 1,2,3,4.5... Не
заглядывая в книжку, не могу сказать, является ли 0 (ноль) натуральным
56

числом. Подозреваю, что это определение меняется время от времени.
Ладно, пусть будут числа не натуральные, а целые: ­3.­2,­1,0,1,2.3.4...
Теперь важный вопрос ­ что будет, если сложить два целых числа?
Правильно, целое число. Вычитание ­ то же сложение, только наоборот. С
умножением проблем опять­таки нет. Обратите внимание, мы не
задумываемся, почему при перемножении двух отрицательных чисел
получаем ­ внезапно ­ положительное. Почему, почему ­ по определению.
6 _
2

А вот с делением всё как­то не так. 3 ~~ , это хорошо и правильно, вот
2

7
— ­ 2 333
только 3 — •­>•­>•­>•••, а это уже ни разу ни целое число.
Далее математики, древнегреческие, которые ещё без штанов, объявили,

7

7

что "J это вовсе не 2.333..., а такое специальное число ­ "3. Это нельзя
понять, над этим надо думать. Это называется рациональное число. Если
поделить любое рациональное число на другое рациональное число, то
опять получится рациональное число.
Рациональное в переводе ­ разумное. Но, как всем известно, если есть
домашние хозяйки, то где­то должны быть и дикие. Если есть
разумные/рациональные числа, то где­то должны существовать и
неразумные/иррациональные. Самое известное ­ и нам и грекам ­
иррациональное число л/2 = 1.41421... Здесь главное понять, что число
здесь не то, что справа от знака равенства, а то, что слева. Именно так это
число и задаётся, а то что справа ­ оно же, но пропущенное через
мясорубку.
Ещё есть числа трансцендентные. Это наши любимые тс, е й прочие
порождения больного разума. Вы легко определите, какие из типов чисел
математических соответствуют каким типам чисел в программировании.
Теперь немного красивых слов. Новое слово группа. Группа это не просто
числа какого­то типа. Группа ­ это числа в комплекте с бинарной
операций, назовём её * . Бинарная операция ­ это хорошее, знакомое
программисту понятие. На самом деле, группа ­ это не обязательно числа,
но не будем занудными. Так вот, группа является группой, если:
57

1.
(A* B)* C = A*(B*C)
Это называется
ассоциативность
и
программисту это неинтересно
2.
Существует нейтральное (нулевое) число 3. При этом

frac

f r a c : TFrac) : i n t e g e r ;
wNum, wDenom : i n t e g e r ) : i n t e g e r ;
wNum
integer
wDenom : i n t e g e r
string;
single;

0;
0) : s t r i n g ;

Комментарии. Один экземпляр класса ­ одна дробь. Num ­ сокращение от
numerator. Denom ­ от denominator. Числитель и знаменатель задаются
непосредственно в конструкторе, задавать их позже через свойства мне
показалось как­то странным и неудобным. Add, Sub, Mul, Dvi ­
соответственно сложение, вычитание, умножение и деление. Деление
называется Dvi, а не Div по очевидным причинам.
Схожие методы, но с суффиксом Imm делают то же, но вторая дробь
задаётся не как класс, а непосредственно. IntPower возводит в целую
степень, Compare сравнивает с другой дробью. toStr и toStrProp переводят
дробь в символьное представление, с той разницей, что toStr выводит как
есть, a toStrProp выделяет целую часть. То есть для дроби 11/3 первый
метод так и переведёт, а второй метод напишет 3 2/3. Метод TpFl, как
нетрудно догадаться, переводит дробь в число с плавающей точкой.
И самый­самый нужный метод ­ Simplify. Он сокращает дробь. Было 3/15,
стало ­ 1/5.
Теперь займёмся реализацией. То, что нам понадобится из дельфийских
модулей:
uses
Math,

SysUtils;

Теперь очень важная функция, не метод ­ определение наибольшего
общего делителя для произвольного набора целых чисел. По­русски это
называется НОД, по­английски, соответственно, GCD. Алгоритм не мой,
какого­то Евклида.
f u n c t i o n GCD(
var
amin
ok

a : array of integer)

: integer;

integer;
boolean;
integer;

61

begin
result:=1;
if

H i g h ( a ) >= L o w ( a ) t h e n b e g i n
f o r i : = L o w ( a ) t o H i g h ( a ) do
a[i]:=Abs(a[i]);
amin:=a[Low(a)];
f o r i : = L o w ( a ) + 1 t o H i g h ( a ) do
i f a [ i ] < amin
then a m i n : = a [ i ] ;

k : = 1 t o a m i n do b e g i n
ok:=true;
f o r i : = L o w ( a ) t o H i g h ( a ) do b e g i n
i f ( a [ i ] mod k ) 0 t h e n b e g i n
ok:=false;
Break;
end;
end;
i f ok
then r e s u l t : = k ;
end;
end;
for

end;

А почему я не вынес объявление этой функции в секцию интерфейса,
вещь­то ведь универсально полезная? Вот вы и вынесите!
Гиви передаст! © Старый анекдот, у меня все анекдоты старые
Сначала конструктор и деструктор. Конструктор
деструктор никакой, потому что незачем:

примитивен,

а

constructor TFrac.Create(
wNum, wDenom : i n t e g e r ) ;
begin
i f wDenom = 0 t h e n wDenom:=100;
fNum:=wNum;
fDenom:=wDenom;
end;

Оценили изящество момента? Если знаменатель задан нулевой, дробь
считается десятичной. Ещё изящнее было бы сделать второй параметр
параметром по умолчания. Если его нет ­ то опа, десятичная дробь!
Далее методы для четырёх арифметических операций:
62

procedure TFrac.Add(
frac : TFrac);
begin
num:=num*frac.denom + frac.num*denom;
denom:=denom * f r a c . d e n o m ;
Simplify;
end;
{
procedure TFrac.Sub(
frac : TFrac);
begin
frac.num:=­frac.num;
Add(frac);
end;
{
procedure TFrac.Mul(
frac : TFrac);
begin
num:=num * f r a c . n u m ;
denom:=denom * f r a c . d e n o m ;
Simplify;
end;
{
procedure TFrac.Dvi(
frac : TFrac);
begin
num:=num * f r a c . d e n o m ;
denom:=denom * f r a c . n u m ;
Simplify;
end;

}

}

}

Просто и практически очевидно. Далее аналоги для непосредственного
задания дроби. Если мы не создаём объект, кто­то ведь его создаёт?
Суровая правда жизни:
p r o c e d u r e TFrac.AddImm(
var
frac
begin
frac:=TFrac.Create(
Add(frac);
frac.Free;
end;
{
p r o c e d u r e TFrac.SubImm(
var
frac
begin
frac:=TFrac.Create(
Sub(frac);
frac.Free;
end;
{
procedure TFrac.MulImm(

wNum, wDenom : i n t e g e r ) ;
: TFrac;
wNum, wDenom);

wNum, wDenom : i n t e g e r ) ;
: TFrac;
wNum, wDenom);

wNum, wDenom : i n t e g e r ) ;

63

var
frac

: TFrac;

begin
frac:=TFrac.Create(
Mul(frac);
frac.Free;
end;
{
procedure TFrac.DviImm(
var
frac
begin
frac:=TFrac.Create(
Dvi(frac);
frac.Free;
end;

Возведение в
предсказуемо:

степень

wNum, wDenom);

wNum, wDenom : i n t e g e r )
: TFrac;
wNum, wDenom);

оказалось

procedure
TFrac.IntPower(
var
oldNum,oldDenom
tmp

pow

немного

сложнее,

что немного

: integer);
integer;
integer;
integer;

begin
oldNum:=num;
if

oldDenom:=denom;

pow = 0 t h e n
num:=1;
denom:=1;

begin

end
e l s e begin
f o r i : = 1 t o A b s ( p o w ) ­ 1 do b e g i n
num:=num * oldNum;
denom:=denom * o l d D e n o m ;
end;
pow < 0 t h e n b e g i n
tmp:=num;
num:=denom;
end;
end;
if

denom:=tmp;

Simplify;
end;

Функции сравнения. Сначала обдумайте, как это работает с
арифметической точки зрения. Потом обдумайте не было бы лучше
сделать результат функции пользовательским типом, или ну его ...
function

TFrac.Compare

(

frac

: TFrac)

64

: integer;

//

­1

Self

< frac;

0 Self

= frac;

+1

Self

>

frac

begin
result:=0;
i f num * f r a c . d e n o m < f r a c . n u m * denom t h e n r e s u l t : = ­ 1 e l s e
i f num * f r a c . d e n o m = f r a c . n u m * denom t h e n r e s u l t : = 0 e l s e
i f num * f r a c . d e n o m > f r a c . n u m * denom t h e n r e s u l t : = + 1 ;
end;
{
f u n c t i o n TFrac.CompareImm(
wNum, wDenom : i n t e g e r )
var
frac
: TFrac;
begin
f r a c : = T F r a c . C r e a t e ( wNum, wDenom);
result:=Compare(frac);
frac.Free;
end;

}
: integer;

А очень важная функция Simplify оказалась очень простой, потому что всё
уже сделано в функции GCD.
procedure
TFrac.Simplify;
var
d
begin
d:=GCD( [ n u m , d e n o m ] ) ;
num:=num d i v d;
denom:=denom d i v d;
end;

: integer;

И, для завершённости, методы конвертирования:
function TFrac.toStr(

wNum : i n t e g e r
wDenom : i n t e g e r

= 0;
= 0) : s t r i n g ;

begin
if

(wNum = 0) a n d (wDenom = 0) t h e n b e g i n
i f num < > 0
then r e s u l t : = I n t T o S t r ( n u m )
+ '/' + I n t T o S t r ( d e n o m )
else result:='0';

end
e l s e begin
i f wNum < > 0
then r e s u l t : = I n t T o S t r ( w N u m )
else result:='0';
end;
end;
{
function TFrac.toStrProp : string;
begin
i f num < > 0 t h e n b e g i n
i f (num d i v denom) < > 0
then r e s u l t : = I n t T o S t r ( n u m
else result:='';

65

+ '/' + I n t T o S t r ( w D e n o m )

}

d i v denom) + ' '

if
end
else

(num mod denom) C
t h e n r e s u l t : = r e s u l t + t o S t r ( num mod

denom,

denom)

result:='C';

end;
{

j

function TFrac.toFl
: single;
begin
result:=num/denom;
end;

Теперь подумает о топ, чего нет, то есть ­ что в этот класс ­ или около него
­ неплохо бы добавить.
Начнём со вполне бессмысленного упражнения, хотя упражнения, они все
такие бессмысленные ­ выделим периодическую часть десятичной дроби.
Для чего это нужно, я точно не знаю, но вдруг? Есть тут и ложка дегтя с
бочкой мёда. Ложка дёгтя ­ перед собственно периодом часто идёт
непериодическая
6 = 0Л

6 6 6 . . .

= 0Л(

часть,
6)

например

­

3

=

0 . 3 3 3 . . .

=

0 . ( 3 )

,

но

. Бочка мёда ­ наш алгоритм будет работать не

только для десятичных дробей, но и для всех дробей с позиционной
записью по любому другому основанию. Как метод класса реализовать это
нецелесообразно, в классе у нас нет десятичного представления дроби,
назначение класса совсем другое. Договорились, реализуем как отдельную
функцию. Абсолютно универсальную функцию вы напишете сами и
потом, пока напишем функцию с ограничениями. Ограничим
непериодическую часть и длину периода, обе длиной 16. Здесь абсолютно
не важно, каким именно числом ограничили мы эти значения,
принципиально, что ограничили. Алгоритм без ограничений должен
сильно отличаться. Или я не прав?
Напрашивается вот такой интерфейс:
const
maxAfter
maxPeriod
minLen
procedure

= 16;
= 16;
= maxAfter +

FindRepetend(

maxPeriod*2;

d
var periodLen
var p e r i o d A f t e r
var period

66

string;
integer;
integer;
string);

Красивое слово repetend, как выяснилось, обозначает повторяющуюся
часть периодтческой дроби. Функция возвращает длину периода, длину
непериодической части и собственно период. Десятичное представление
дроби я решил представить в виде строки, мне так нравится. Очень важный
момент ­ периодическая дробь всегда бесконечна, просто по определению,
поэтому я имею наглость ожидать, что на вход нашей функции будет
предоставлен достаточно длинный её кусок, не менее maxLen, По такому
случаю я даже отойду от своих принципов и добавлю соответствующую
проверку на входе в функцию. Кроме того, мне кажется, что достаточно на
вход задать только дробную часть, то есть не '0.33333', а '33333'.
Безусловно, 16 слишком мало даже для очень несложных дробей. Но так
удобнее для отладки, константы, повторю, можно заменить. Существует
важное понятие ­ сложность алгоритма. Сложность, как ни странно,
никак не зависит от времени, которое потребуется читателю, чтобы этот
алгоритм понять. Обычно всё строго наоборот. Сложность ­ это
приблизительная, на пальцах, где­то так, зависимость количества
необходимых вычислений от количества данных. Например, у любимой
мною пузырьковой сортировки сложность квадратичная. Это значит, что
если вместо массива из 37 элементов мы захотим отсортировать 3700
3700 _
)
(
элементов, то время работы программы увеличится в 37
~~ 1 0 0 0 0
раз. Это всё к тому, что наш алгоритм имеет почти линейную сложность, а
лучше линейной сложности ничего не бывает. Вывод ­ константы можно
менять смело.
(

)2

Программирование чего угодно должно начинаться с программирования
тестов для него. Тестировать будем следующие варианты:
Смысл
Период начинается сразу,
длина периода = 1
Период начинается сразу,
длина периода > 1
Есть непериодическая часть,
длина периода = 1
Есть непериодическая часть,
длина периода > 1

Дробь
натуральная
1/3

Дробь
десятичная
0.(3)

Длина
периода
1

1/7

0.(142857)

6

1/15

0.0(6)

1

1/26

0.03(571428)

6

67

Вот тестовая программа, на все случаи сразу ­ временно ненужное
закомментировано:
var
d
periodLen
periodAfter
period

:
:
:
:

string;
integer;
integer;
string;

begin
d:='33333333333333333333333333';
(*
d: =
'14285714285714286714285714';
d:='06666666666666666666666666';
d: =
'03571428571429571428571428';

*)
FindRepetend( d, p e r i o d L e n , p e r i o d A f t e r ,
ShowMessage(

'd = ' + d +
'periodLen
'periodAfter
'period

period);

#13#1C +
= ' + I n t T o S t r ( p e r i o d L e n ) + #13#1C +
= ' + I n t T o S t r ( p e r i o d A f t e r ) + #13#1C +
= ' + period);

He считайте меня занудным и унылым, как опыт учит нас ­ если что­то
можно понять неправильно, оно будет понято неправильно. Это я про
требования к заданию десятичного представления. Кроме того,
признайтесь, вы ведь даже не просмотрели этот код? Вот и я тоже, я ведь
его написал.
Возвращается чукча из Москвы:
­ Однако, чукчу в Союз Писателей приняли!
­ Да ты же читать не умеешь.
­ Однако, чукча не читатель, чукча писатель!
© Любимый анекдот чукчи
Это я к тому, в исходном коде теста уже содержится ошибка. Однако,
бывает. Если присмотреться внимательнее, ошибок даже две. Теперь о
реализации. Если вы не можете рассказать алгоритм словами, значит вы не
сможете его запрограммировать. Нет, показать руками и на пальцах ­ не
годится. Идея такая: предполагаем непериодическую часть равной нулю.
Перебираем длины периода от 1 до maxLen. Если период действительно
период, то Ok, если нет увеличиваем длину периода. Если всё равно не Ok,
увеличиваем длину непериодической части. По­моему, всё просто и
очевидно. Получилось вот такое:
68

procedure

FindRepetend(

d
var periodLen
var p e r i o d A f t e r
var period

string;
integer;
integer;
string);

var
Ok
ch
i

boolean;
char;
integer;

begin
periodlen:=C;
periodAfter:=C;
period:='';
if

L e n g t h ( d ) < minLen then

Exit;

repeat
periodLen:=periodLen + 1;
p e r i o d : = C o p y ( d, p e r i o d A f t e r + 1 , p e r i o d l e n ) ;
Ok:=true;
f o r i : = p e r i o d A f t e r + 1 t o L e n g t h ( d ) do b e g i n
c h : = p e r i o d [ ( ( i ­ p e r i o d A f t e r ­ 1) mod p e r i o d L e n ) + 1 ] ;
i f d [ i ] c h t h e n b e g i n
Ok:=false;
Break;
end;
end;
i f ( n o t Ok) a n d ( p e r i o d L e n >= m a x P e r i o d ) t h e n b e g i n
p e r i o d A f t e r : = p e r i o d A f t e r + 1;
periodLen:=C;
end;
u n t i l Ok o r ( p e r i o d A f t e r > m a x A f t e r ) o r ( p e r i o d L e n >= m a x P e r i o d ) ;
n o t Ok t h e n b e g i n
periodlen:=C;
periodAfter:=C;
period:='';
end;
if

end;

Что вы думаете об это тексте? Почему в условии periodLen >= maxPeriod
присутствует знак больше? Говорит ли это о моей повышенной
тревожности и мнительности? Мучают ли меня кошмары или я ими
наслаждаюсь? Демонстрирует ли финальный условный оператор мою
занудность тоскливость или, наоборот, аккуратность и внимание к
мелочам?
Задания для самостоятельной работы:

69

Оцените всё­таки сложность алгоритма ­ линейная или квадратичная, или
что? Можно теоретически, можно опытным путём.
­
по
периодической
дроби
найти
Сопутствующая
задача
соответствующую ей рациональную дробь ­ в этом уже есть некоторая
польза.
7

Обратная величина ­ это просто. Составные дроби, то есть

7

40
и

работа с

ними. Оно нам надо?
Цепные дроби. Попутно узнайте, что это такое. Красиво и полезно.
Подумайте о переводе натуральной дроби в другую систему счисления, то
2
_2_
есть 9

~~ 1о 0з. Если вы понимаете это по­другому, то обоснуйте. В

любом случае, красиво, но бесполезно.
Обдумайте порождённый класс, в котором в числителе и знаменателе не
целые числа, а многочлены. А ещё лучше ­ аналитические функции.
Очень длинные числа
Выполним незатейливое упражнение, это даже программой назвать
трудно:
var
int1,int2,int3

: integer;

begin
int1:=1000;
int2:=2;
int3:=int1 div int2;
ShowMessage( I n t T o S t r ( i n t 3 ) ) ;
end;

Что будет, если вместо 1000 (тысячи) написать 100500000000 ( что пятьсот
миллиардов)? Правильно, компилятор на пропустит. Далее пример чуть
сложнее:
int1:=1000;
int2:=2;
int3:=int1 * int2;
ShowMessage( I n t T o S t r ( i n t 3 ) ) ;

70

Теперь подрисуем три нуля к первому сомножителю и сколько надо ­
например пять ­ ко второму. Что будет? Понятно что, компилятор
пропустит, но в результате выполнения получим ошибку. Что­то
переполнилось через что­то.
Обратите внимание на главное ­ с точки зрения математики и даже, не
побоюсь этого слова, арифметики. У нас всё чисто. Всё должно работать ­
но не хочет. Потому что числа длинные. В первом случае они просто
слишком длинные, что бы поместиться в integer. Во втором случае сами по
себе они хоть куда, но произведение их в integer уже не вмещается. Задача
­ написать класс или набор процедур, позволяющих задать числа хоть в
тысячу или миллион знаков и произвести над ними четыре основные
арифметические операции. Заметьте, при этом становится не очень
важным,
являются
ли числа
целыми,
рациональными
или
действительными.
Задача не выдуманная, задача реальная. В ответ на вопрос ­ а зачем,
собственно? ­ обычно отвечают ­ а если вам захочется вычислить число тс
с миллионом знаков? Ответить нечего. Называется это вычисления с
большим количеством знаков или просто ­ длинная арифметика.
В уважающих себя языках программирования средств для работы с
длинной арифметикой нет. Сторонних библиотек ­ в ассортименте. Я
решил попробовать написать свою. Обязательное требование ­ чтобы
работала не очень медленно. Сложение и вычитание ­ элементарно.
Умножение ­ немного сложнее. На делении я задумался. Реализовать
просто, но вот чтобы не очень медленно. Поскольку лимит незаполненных
страниц в книге иссякает, я эту мысль оставил, заглянув напоследок в
многократно упомянутую книгу
Ч.Уэззерел "Этюды для программистов",

М, Мир, 1982

В главе 7гэр Квадрат нашёлся раздел Что можно сказать
деления?

относительно

Уэзерелл оценивает трудоёмкость написания библиотеки в 5 недель для
одного программиста. Попробуйте, вам же делать нечего!

71

Глава 3
Математическая логика.
А надо? Пожалуй, всё­таки надо
Вступление в логику
Вот эта глава уже стопроцентно математическая. Она о математической
логике. Однако логика бывает разная. Перед написанием этой главы я
прочитал (за вас) все нужные книги и выяснил следующее. Логика бывает:
­ Человеческая или бытовая
­ Аристотелева
­ Школьного учебника сталинских времён
­ Из учебника для техникумов (теперь колледжей) по специальности
« программист»
­ Для студентов пединститутов
­ Математическая логика для университетов, которой меня,
собственно, и учили
­ Ещё есть курс А.В.Гладкого, который показался мне очень
занимательным, но он отсканирован настолько небрежно, что у меня
глаза вылезли наружу на стебельках и собрались в кучку.
Далее обо всём понемногу и по порядку.
Человеческая логика полезна и необходима для каждого. Это умение
рассуждать здраво. Думать надо меньше, а соображать больше, как
сказано в популярном фильме. Особенно важно это как раз для
программистов. Впрочем, всю программистскую логику можно свести к
одной цитате:
У каждого чрезвычайного происшествия есть имя, отчество и фамилия
© Сталинский нарком М.Л.Каганович
Если программа не работает ­ значит в ней есть ошибка. Если есть ошибка
­ у неё есть автор. И почаще повторяйте ­ Чудес не бывает!
Логика Аристотеля. Основное сочинение Аристотеля называется Органон.
Надо пояснить, что сочинения Аристотеля делятся на три категории ­ те,
что написал вроде бы он, те, что написал вроде бы не он, и те, что написал
72

точно не он. Органон относится к первой категории. Что можно сказать об
Аристотеле, если вас кто­то спросит, конечно. Смело отвечайте ­
Аристотель доказал, что у мухи восемь ног. Поймайте муху и отрывайте по
одной ­ ног шесть. Потом положите тельце на стол и ударьте по столу
кулаком ­ муха не улетает. Следовательно, муха слышит ногами. Это и
есть Аристотелева логика. Не брат ты мне, Аристотель, философ
греческий.
// Философическое размышление
Когда я был не то, чтобы маленьким, и даже совсем не юным, я сдавал
кандидатский минимум по философии. Преподавательница наша была ещё
юнее нас всех и в больших очках, хотя и с другими достоинствами.
Взгляды у неё были самые либеральные и прогрессивные ­ это были
забавные девяностые. В качестве обязательного чтения
нам
предписывались три книжки. Немецкий философ Ясперс (основной тезис ­
осевое время), грузинский философ Мордашвили (основной тезис ­
русское свинство), третьего забыл, но прочитал.
В результате на экзамен я принёс Краткий философский словарь
пятьдесят четвёртого года издания, который мы все радостно и списывали,
пока преподавательница ушла греться. Лично у меня первый вопрос был
как раз по Аристотелю. Экзамен мы сдали на отлично. Не подкачал старый
словарь!
Кстати, согласно преданию, в словаре написано, что Кибернетика ­
продажная девка империализма. Это не соответствует действительности.
Не в словаре, а в журнале Вопросы философии. И написано там
всего­навсего Кибернетика ­ прислужница мракобесов.
// конец Философического размышления
Сталинский школьный учебник. С.Н.Виноградов, А.Ф.Кузьмин «Логика.
Учебник для средней школы», издание восьмое, M., Государственное
учебно­педагогическое
издательство
Министерства
просвещения
РСФСР, 1954
Сталин умер в пятьдесят третьем, так что учебник с полным правом
сталинский, и цитаты из И.В.С. там есть, в качестве образцов логических
рассуждений. Не идеальных, но вполне приемлемых, в семинариях всё же

73

не груши учили околачивать. Отменили преподавание логики в школах
вскоре после смерти Сталина.
Сначала надо отжать применение логики к актуальной политике,
национальному вопросу и мировому рабочему движению. После этого
учебник сводится к предыдущему пункту ­ Аристотелевой логике. С
одной стороны в учебнике встречаются неизвестные (мне) слова ­
например, энтимема. Word этого слова не знает, я не знаю, а школьник
через десять лет после войны должен был знать. Это, наверное, плохо. С
другой стороны, отдельная глава выделена нечестным методам дискуссии,
сохранившимся с аристотелевских времён, пережившим сталинские и
процветающим теперь. Как­то, переход на личности ­ твоя бабушка
чекистка, а сам ты злобный жидоупыръ, подмена тезиса ­ зато спгваем
гарно, и так далее и тому подобное. Это очень полезное знание, во всех
отношениях.
Проработав учебник, неплохо попытаться перевести его содержание на
язык множеств. Кстати, я просмотрел современное ЕГЭ по математике и не
нашёл там вопросов по логике. Я плохо искал?
Теперь учебник для техникумов. И.Л.Никольская
«Математическая
логика»,
М.
«Высшая
школа».
Допущено
Министерством
приборостроения, средств автоматизации и систем управления в
качестве учебника для техникумов по специальности
«Прикладная
математика».
Тональность добрая и ласковая. Дескать, не плачь, мой маленький дружок
из ухрюпинского заборостроительного техникума. Сейчас я тебе всё­всё
объясню, недоумку. И объясняет. В общем, неплохая книга, стоит
прочитать.
Меня заинтересовал свежий подход ­ конструирование для логического
выражения эквивалентной ему электрической схемы. У других авторов
такой идеи я не помню. Основная проблема этого учебника та же, что и у
всех других ­ совершенно непонятно, зачем эта логика вообще нужна и как
это всё применять.
Из учебника для пединститутов, учебника для университетов и
монографии я ничего более полезного уже не узнал. Рекомендую
74

остановиться на техникуме. Дальнейшее изложение от моего лица будет
идти
строго
на уровне
этого
самого
фанеростроительного
учебно­лечебного заведения.
Исчисление предикатов, алгебра высказываний и прочее.
Звучит страшно, а так нет
Начать можно по­разному. Есть булева алгебра, есть алгебра логики, есть
логика высказываний. Надо четко запомнить, что всё это совсем разные
вещи. И тут же забыть, потому что в программировании всё это
превращается в одно и то же. Объясняю.
Берём в руки предмет ­ лягушку. У вас есть лягушка? У меня есть лягушки,
домашние животные, узнают в лицо и руками показывают, что хотят
жрать. Формулируем высказывание Это лягушка, обозначим его Л,
потому что какая разница, как его обозначить. Поглядев на предмет,
замечаем, что он зелёный. Возражать не надо, я знаю, что лягушки не
обязательно зелёные, конкретно мои розовые и с когтями. Формулируем
высказывание Это зелёное, обозначим высказывание 3.
Поглядев на предмет в руках, мы можем определить истинность нашего
высказывания. Если у нас в руках лягушка, то высказывание Л является
истинным. Если мы крепко сжимаем в мозолистойруке степлер, то
высказывании Л ложно. То же самое с высказыванием 3. Если лягушка
розовая, 3 ложно. Если степлер зелёный, высказывание 3 истинно. Пока
всё не просто, а очень просто.
Напишем вот такое: —Л. Кочерга спереди обозначает отрицание. В
переводе на человеческий означает Это не лягушка. Выражение является
истинным тогда и только тогда исходное выражение ложно. Если у нас
лягушка, то ­лЛ ложно, если степлер ­ истинно.
На этом этапе неплохо задуматься, как обозначать истина и ложь. Часто
используют цифры 1 и 0. В этом есть свой резон, но, по понятным
причинам я предпочитаю true и false. То есть запись ./7=false эквивалентна
Это не лягушка. Обратите внимание, если JT=false, то обязательно
^/7=true. По другому можно это сформулировать ­1(­Л)оЛ.
Знак

75

означает эквивалентно, или другие, более циничные соответствия. Бывает,
что вместо ­лЛ пишут Ё .
Отрицание является унарной операцией ­ говоря по­русски, оно
применимо только к одному высказыванию. Есть операции и для двух
высказываний ­ при желании, их можно выдумать целых пять, но нам
хватит двух ­ всё равно адекватные языки программирования больше не
поддерживают.
Для начала немного расширим ассортимент нашего зоопарка.
Высказывание Это жаба обозначим Ж. Высказывание Это серое
обозначим С. На всякий случай, если вы не любите Родную Природу так,
как люблю её я, поясню ­ типичная жаба серого цвета. Что написать, если
вы хотим выразить мысль Это лягушка и это зелёное? Есть варианты.
Можно так: ЛлЗ. Можно так Л&З. Эта формула истинна тогда и только
тогда, когда истинны оба её операнда ­ то есть у нас в руках зелёная
лягушка. Если у нас голубая лягушка и степлер произвольного цвета ­
формула ложна, иначе говоря, имеет значение false.
По­другому это называется логическое умножение или конъюнкция или
логическое
и.
Отрицание
для
конъюнкции
выглядит
так:
­п(ЛХ^)(—u/7)v(—к?). Тут надо сделать два замечания. Во­первых, знак v я
объясню чуть дальше. Во­вторых, преобразование это советую запомнить,
потому что в программировании оно встречается на каждом шагу. Ну не на
каждом, но часто.
Теперь наша математическая логика заметно удаляется от логики бытовой.
Вот такое с виду безобидное высказывание: Это лягушка или это жаба.
Формула выглядит так: ЛчЖ. Напоминаю, если вы забыли ­ лягушка и
жаба совсем разные животные. С бытовой точки зрения это означает одно
из двух ­ или лягушка или жаба. С математической к этому добавляется
версия, что оно и то и другое одновременно. Такого не может быть? Ещё
более бытовая версия: Я приду во вторник или в среду. Простой человек
будет ждать незваного гостя только в один из двух дней. Специалист по
математической логике припрётся два раза ­ и во вторник, и в среду.
Это называется логические сложение или дизъюнкция или логическое и.
Формула ложна тогда и только тогда, когда ложны оба операнда. Во всех
76

других случаях формула истинна. Отрицание для
­п(Л\/Ж)(­к/7)& (­Ж). Аналогично ­ советую запомнить.

дизъюнкции:

Для расширения кругозора ­ вашего ­ добавлю, что существует ещё и
строгая дизъюнкция, она же разделяющая. Мы это презираем и
игнорируем, всё равно в популярных языках программирования этого нет.
Всё, что осталось понять ­ что всё это может сочетаться меж собой до
любого уровня сложности. Например, (JI&3)v(}K&C) характеризует вас
как любителя классики, но человека при этом непривередливого ­ вам всё
равно, что жаба, что лягушка, лишь бы цвета были строго каноническими.
Теперь временно отложим математику в сторону и поглядим, что всему
этому соответствует в Delphi, В C++ всё то же самое, вот только
обозначения другие. Объявляем две переменные булевского типа, одна для
лягушки, другая для зелёного цвета, третья для результата:
var
frog
green
rez

: boolean;
: boolean;
: boolean;

Истинность. Лягушка ­ да, Зелёная ­ нет.
frog:=true;
green:=false;

Отрицание ­ не Лягушка:
rez:=not

frog;

Конъюнкция ­ Лягушка и Зелёная
rez:=frog

and g r e e n ;

Дизъюнкция ­ Лягушка или Зелёная ­ то есть, на выбор: зелёная лягушка,
розовая лягушка, зелёный степлер.
r e z : = f r o g or green;

Хитрое выражение (Л&3)\/(Ж&С)
rez:=(frog

and g r e e n ) o r ( t o a d

and g r a y ) ;

77

Теперь немного о таблицах истинности. Попросту говоря, таблица
истинности ­ это такая шпаргалка для альтернативно одарённых. На вход
один, два или больше логических (булевских) операндов. На выходе
значение некоторой формулы. Для каждой формулы таблица истинности
своя. Примеры.
Для отрицания:
A
true
false

^A
false
true

Чуть сложнее, для дизъюнкции:
A
true
true
false
false

B
true
false
true
false

AvB
true
true
true
false

И ещё сложнее, операндов теперь четыре, и формула уже наша
собственная, нетривиальная ­ (Л&3)ч(Ж&С). Выпишу только первые
шесть строк, дальше продолжите сами:
Л
true
true
true
true
true
true

3
true
true
true
true
false
false

ж

true
true
false
false
true
true

с
true
false
true
false
true
false

(Л&3)\/(Ж&С)
true
true
true
true
true
false

Как несложно заметить, если число операндов N , то строк в таблице
истинности будет 2 .
N

А что такое предикат? Да вот это всё и есть предикаты. Если мы пишем
truevfalse=true, то это не предикат, а если пишем Л\/Ж = неведома
зверушка, то это предикат, потому что мы не знаем заранее, чему равно Л и
чему равно Ж.
78

Теперь о том, о чём написать надо, так какая разница ­ где об этом
написать? Я о кванторах. Формально это относится к математической
логике, но, по сути, используется везде, но только для упрощения записи.
Квантор общности ­ V. Демонстрирую на примере: VNe (множество
целых чисел}, что N

3

>N

2

> N. В переводе это значит, что для любого

целого, например 7, имеем 7 > 7 > 7 . Это высказывание является
истинным, что вовсе не обязательно, поменяйте знак на противоположный
и убедитесь.
3

2

Квантор существования ­ 3. Пример: 3N, что 2 =1024 , то есть где­то
есть, безусловно есть, оно не может не есть, такое N , что если в него
возвести двойку, то получим ровно килобайт.
N

Возвращается Чапаев из Москвы после сдачи экзаменов в Академию.
­ Нет Петька, не сдал. Спрашивают, сколько будет ноль пять плюс ноль
пять. Нутром чувствую, что литр, а доказать ­ не могу...
© Народное математическое
Обычно кванторы используются не по одному, а кучно, вот так нас учили
на первом курсе определять предел функции:
Iim f (x) = F \/s3S

> 0,что Vx,что | x ­ x |< 8 =>| f (x) ­ F \ 31) a n d ( b l a c k R o w > 31) t h e n w i n B a n k : =
e l s e begin
i f redRow­30 < b l a c k R o w ­ 3 0 t h e n b e g i n
winRed:=winRed + 1;
end e l s e
i f redRow­30 > b l a c k R o w ­ 3 0 t h e n b e g i n
winBlack:=
winBlack + 1
end
e l s e begin
//

else

winBank+1

replay

end;
end;
D.Free;
end;
ShowMessage('Red/Black/Bank = ' + I n t T o S t r ( w i n R e d ) + ' / ' +
I n t T o S t r ( w i n B l a c k ) + '/' +
IntToStr(winBank) + ' / ' ) ;

Обратите внимание на тот факт, что создание и уничтожение объекта
находится внутри основного цикла. Сначала я поместил это всё снаружи
цикла и изумлялся тонким неожиданным эффектам в процессе
выполнения программы. Как вам кажется, есть ли ещё ошибки в
программе? Мне кажется, что нет. Это я спрашиваю к тому, что результат
меня очень удивил. Вот он, результат, оформленный в таблицу.

151

Число
Красное
испытаний
1000
228

Чёрное

Банк

Ч­%

К­%

Б­%

250

485

22.8%

25.0%

48.5%

10000

2296

2350

4887

23.0%

23.5%

48.9%

100000

23571

23216

48689

23.6%

23.2%

48.7%

1000000

233035

231899

489478

23.3%

23.2%

48.9%

Результат, мягко говоря, поражает. Шансы красного и чёрного можно
считать равными, что естественно ­ если бы шансы красного были выше,
кто поставил бы на чёрное? Но вот прибыль банка... Если при игре в
48.9% ^
рулетку выигрыш банка скромные 2.70%, то здесь имеем
2
*
%.
2 4 . 5

Деление на два необходимо потому, что банк забирает не всю ставку, а
только половину. Всё равно ­ грабёж среди бела дня!
Будучи в тупике, я полез в англоязычный интернет, там эта игра
называлась чисто по­европейски Trente et Quarante, и, буквально во второй
строке, отмечалось, что игра эта очень даже честная, игрокам уходит более
98% остальное ­ менее 2% ­ банку . Я удивился. Далее выяснилось, что
играют сразу шестью колодами по 52 карты, что, мне кажется, не вносит
ощутимых изменений. Есть ещё нюансы, столь же незначительные.
Главное ­ банк выигрывает только в случае, если оба ряда равны 31, а это
уже существенно. Меняем строку
if

(redRow > 31) and ( b l a c k R o w > 31) t h e n w i n B a n k : =

winBank+1

(redRow = 31) and ( b l a c k R o w = 31) t h e n w i n B a n k : =

winBank+1

на
if

Запускаем, последняя строка таблицы трансформируется:
Число
Красное
испытаний
1000000
445285

Чёрное

Банк

Ч­%

К­%

Б­%

443332

18332

44.5%

44.3%

1.83%

Это очень хорошо совпадает с только что прочитанным в английской
Википедии, про выигрыш банка меньше двух процентов. К сожалению,
152

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

2 . 6 3

%.

Кто­то

где­то неправ.
Теперь второй вопрос, точнее два вторых вопроса. Сначала простой
вопрос ­ какова вероятность выпадения красного 17 раз подряд?
р = 4 т = — — « 0.00000763« 0.000763%
2
131072
И так было ясно, что вероятность не маленькая, а очень маленькая.
Поэтому второй вопрос, тот же, что был задан Ходже Насреддину ­
насколько правдоподобно утверждение, что такого события не было
пятнадцать лет?
1

17

17

Посчитайте сами, мне надоели эти однообразные механические
вычисления ­ см. анекдот Опять эти нелепые телодвижения.
Вопрос номер три, чисто арифметический. Персонажи проиграли ­
вдвоём, но на одой руке ­ «413000. В те времена в моде была стратегия
удвоения ставок ­ считалось, что при этой стратегии проиграть
невозможно, если, конечно, хватит денег. В чём идея? Игрок ставит франк
на красное, проигрывает, ставит два франка на красно, проигрывает,
ставит четыре франка... И вдруг выигрывает! Теперь считаем, поставил
игрок 1+2+4=7, а выиграл целых 8! Профит ­ целый франк, или 27 копеек
серебром! Дела пошли на лад! Скорее всего, персонажи играли по этой
схеме, поскольку утверждают, что их погубила серия. Можно ли узнать, с
какой начальной ставки они начали, или Верн запутался в расчетах?
Общий проигрыш за N игр 2 — 1 , единицу игнорируем, персонажи
считают в тысячах, та что для начальной ставки в один франк имеем
N

2 = 131072, для ставки два франка 262144, для трёх франков 393216, для
четырёх выходим за пределы указанной суммы. Или играли они как­то не
так, или автор обсчитался.
17

Задача из Мостселлера:
Браун всегда ставит один доллар на номер 13 в американской рулетке,
вопреки совету своего благожелательного друга. Чтобы отучить Брауна
153

от игры в рулетку, этот друг спорит с ним на 20 долларов, утверждая,
что Браун останется в проигрыше после 336 игр. Имеет ли смысл Брауну
принять такое пари?
В американской рулетке 38 номеров. Последние три в пользу заведения.
Очко
Сначала, чисто на всякий случай, напомню правила. Играют малой
колодой в 36 карт. Каждая карта оценивается во сколько­то очков. О
шестерки до десятки ­ по номиналу, валет ­ два, дама ­ три, король ­
четыре, туз ­ одиннадцать. Если игрок набирает 21, то однозначно
выигрывает. Если больше, однозначно проигрывает. Если меньше,
выигрывает тот, у кого больше, ну вы поняли, да?
Вы видели в ютубе, как кубинские негры поют Владимирский централ? Я
это слышал в натуре. Я люблю Кубу. Теперь о главном. Как поётся в песне
­ не очко нас губит, а к одиннадцати туз. Вопрос элементарный ­ какова
вероятность поднять к одиннадцати туза? Несмотря на всю кажущуюся
элементарность игры, вопрос совсем не элементарен.
Примем для рассмотрения традиционный вариант правил, как играли в
моём детстве и что мы видим в фильме Операция Ы ­ к сожалению,
вставить в текст видео пока невозможно. Это означает, что в нашей
ситуации игрок берёт карты из полной колоды, у него набирается карт на
одиннадцать очков. Вопрос тот же ­ какова вероятность поднять туза?
Вопрос не так прост, поскольку вероятность зависит от того, каким
образом набраны одиннадцать очков. 11 очков ­ это может быть один туз,
тогда в колоде три туза, а может и не быть туз, тогда все четыре туза в
колоде. Других карт для искомой суммы требуется от двух ­ семь, король,
например ­ до пяти ­ четыре валета и дама. От числа взятых карт, как легко
догадаться, зависит вероятность получения туза. Но, опять­таки, разные,
формирующие одиннадцать, комбинации, выпадают совсем не с равной
частотой. Короче, Чума на оба ваши дома! © Шекспир, вроде бы.
Используем метод Монте­Карло.
Применяем модифицированный объект ­ колода на тридцать шесть карт.
Смастерите сами, если по другому не получится, разрешаю просто
размножить и поменять константы и. главное, имя класса.
154

var
TDeck36;
TCard;
integer;
integer;
boolean;
integer;

D
card
sum
numEleven,numElevenAndAce
gamover

function Price
card
begin
r e s u l t : = 0;
i f c a r d . rank = s i x
i f c a r d . rank = seven
i f c a r d . rank = e i g h t
i f c a r d . rank = nine
i f c a r d . rank = ten
i f c a r d . rank = Jack
i f c a r d . r a n k = Queen
i f c a r d . rank = King
i f c a r d . r a n k = Ace
end;
{
begin
n u m E l e v e n:=0;
n u m E l e v e nAndAce:=0;
for

TCard)

then
then
then
then
then
then
then
then
then

i : = 1 t o 1 0 0 0 0 0 0 do
D:=TDeck36.Create;
sum:=0;

integer;

result:
result:
result:
result:
result:
result:
result:
result:
result:

=6
=7
=8
=9
= 10
=2
=3
=4
=11

else
else
else
else
else
else
else
else

begin

gamover:=false;
repeat
D.RandomCardWithoutReturn(card);
sum:=sum + P r i c e ( c a r d ) ;
i f sum > 1 1 t h e n b e g i n
Break;
end e l s e
i f sum = 1 1 t h e n b e g i n
numEleven:=numEleven + 1;
D.RandomCardWithoutReturn(card);
i f c a r d . r a n k = Ace
then numElevenAndAce:=numelevenAndAce
Break;
end;
u n t i l gamover;

+ 1;

D.Free;
end;
ShowMessage(

'
'
'

n11 = '
n11Ace =
% = '

'

+ IntToStr(numEleven) +
+ IntToStr(numElevenAndAce)
+ FloatToStrF(

155

+

(numElevenAndAce/numEleven)*100, f f G e n e r a l ,

5,2));

Комментарии. Ценовая функция почти та же. Из цикла repeat­until мы
никогда не выйдем законным способом. Хорошо ли это? Плохо. Но цикл
простой, а один раз ­ не водолаз. На выходе результат.
Одиннадцать К одиннадцати Вероятность
Число
испытаний
туз
туза к 11

Вероятность
ситуации
в игре

1000000

2.50%

1234408

24981

10.66%

Как видим, вероятность прихода туза к одиннадцати достаточно велика ­
при условии что одиннадцать уже есть. Вероятность того, что это вообще
случится в игре намного ниже, но вполне существенная.
Здесь должен быть разбор эпизода из фильма Чрезвычайное поручение,
1965, про героя­революционера Камо, где он героически обыгрывает
батьку Махно или кого­то вроде него в очко. Пересмотрите и
проанализируйте математически. Попутно прочтите книгу Грач птица
весенняя, о революционере Баумане, названным так в честь одноимённого
высшего учебного заведения. Обдумайте эпизод игры в карты, он близко
от начала.
Покер
В покер играют карточной колодой. Есть ещё какой­то позорный покер на
костях, я в это не играл, но презираю. Обычно карт 52, но бывает и 54 ­
тогда в колоде есть ещё два джокера. Джокер это такая хитрая карта,
которую игрок может объявить любой нормальной картой по своему
желанию. Игрок получает пять карт. Карты могут образовать какую­то
комбинацию, комбинаций много. Например двойка ­ две восьмёрки или
два туза. Две двойки ­ две восьмёрки и два туза. Тройка ­ три восьмерки
или три туза. Одни комбинации лучше других. Тройка лучше двух двоек, а
две двойки лучше просто двойки. Выигрывает тот, у кого самая лучшая
комбинация. Есть ещё и ещё дополнительные правила, но для нас они
неважны.
Покер игра американская, потому и воспет в американской литературе.
Профессор Китайгородский анализирует покер на фрагменте романа
Джека Лондона Время не ждёт. Я, для разнообразия, использую
156

полусоветского писателя Александра Грина. Полу ­ потому что первую
половину своего творческого пути он провёл при царизме, а вторую
половину ­ при Советской власти. Оба государственных строя он очень не
любил, и они отвечали ему тем же. Только царский режим Грина сажал и
отправлял в ссылку, а советский бил самым больным способом ­ не давал
денег. Грин был романтик ­ то есть пил очень много. Лучше всего у него
получались романы ­ Алые паруса, Золотая цепь, Бегущая по волнам.
Рассказы не так известны, я возьму для рассмотрения совсем который не
очень ­ Серый автомобиль. Смысл первой половины ­ главный герой
приходит в казино и играет в покер на бешенные деньги. Его противник ­
мулат, что добавляет экзотики в сюжет. Ставка ­ миллион долларов, это в
1925­м
году!
Мне
столько
не
выпить.
Далее
следует
высокохудожественная сцена:
Ронкур смотрел на меня взглядом птички, зрящей змею. Наступил
момент открыть карты. Игроки, заключавшие пари, перестали дышать.
— Ну, — сказал я, смеясь, — Гринъо, выкладывайте ваше каре! Он
перевернул карты, пристукнув кистью руки, так что туз отлетел в
сторону. Но там их было еще три — каре из тузов, вот что было в его
руках! Бешеный рев покрыл это движение
— Вы ошиблись, — сказал я, показывая свои пять с улыбающимся чертом
и раскладывая их одна к одной. — Гринъо, нравится вам этот
джентльмен?
Момент не поддается изображению. Я не слышал криков и воплей, так
как наслаждался бесконечно выражением лица опешившего мулата.
— Ваша... — сказал он сквозь звуки, напоминающие вой. Затем он
откинулся, глаза его закатились... он был в обмороке.
Переводим на человеческий язык. Мулат Гринъо выложил четыре туза и
был уверен в выигрыше. Главный герой выкладывает четыре одинаковых
карты ­ конкретно четыре семёрки, но это неважно ­ и джокера, что даёт
пять семёрок и уходит с миллионом.
Вопрос номер один ­ какова вероятность такого расклада вообще? Вопрос
номер два, на самом деле номер один, потому что он проще ­ какие были
шансы проиграть у мулата Гриньо?
В процессе игры игрок может поменять несколько своих карт. Рассказчик
бросает все пять карт и берёт вместо них пять из колоды, так что
157

интуитивно кажется, что обмен почти никакого влияния на вероятность не
имеет. На том и порешим. Мулат меняет четыре карты из пяти. Легко
догадаться, что у него был один туз и он поднял к нему ещё три.
Напрашивается вопрос номер три ­ какова вероятность события?
Вопрос номер последний ­ стоит ли эмулировать эту фигню программно,
или мы никогда не дождёмся ответа ­ вследствие крайней
маловероятное™ события? Приступим, однако. Надо заметить, что Грин,
похоже, сам в покер не играл, а прочитал о нём в какой­то книжке о
красивой заграничной жизни, так что некоторые детали приходится
домысливать и корректировать за писателя. Грин, к примеру, полагал, что
играют колодой в 53 карты ­ 52 обычных и джокер. Джокеров в колоде два,
так что для нашего игрока шансы заметно повышаются.
Сколькими способами можно сдать пять карт из 54­х?
C

=

=> C

kI(n ­ k)I

5
5 4

=


= 3162510
51(54 ­ 5)!

Сколько существует комбинаций из пяти карт таких, что четыре из них
тузы и ещё одна всё равно какая, но не джокер? Четыре туза набираются
только одним способом, ещё в колоде остаются кроме джокеров 47 карт,
значит 47 комбинаций, поскольку порядок карт нас не волнует.
P

=

3162510 *

. Или, по другому, приблизительно один раз на

0 . 0 0 0 0 1 4 8

67000. Это без учёта обмена карт.
Какова вероятность получить четыре одинаковых карты (иначе говоря,
каре), любые, и один из двух джокеров? Каре набирается тринадцатью
способами, но тузы на руках у другого игрока, так что остаётся двенадцать.
Джокеров
два, в итоге
12 х 2 = 24 . Искомая
вероятность
P

=

3162510 *

0

.

0

0

0

0

0

7

5

9

, или один раз на 131 тысячу случаев, всего в

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

Вероятность обеих комбинаций в одной игре посчитайте сами. Кстати, у
Грина в рассказе никто не верещит, что не припомнит такого и никогда
такого не было. Ну такое, ну подумаешь...
Теперь вероятность поднять трёх тузов к одному, или, что то же самое, к
любой карте три других того же достоинства. Метод тот же. В колоде
осталось 49 карт, берём четыре. Обратите внимание, то, что физически в
колоде не 49, а гораздо меньше, ведь часть карт на руках игроков, что
совершенно неважно.

C

4
4 9

=

49

49I


41(49 ­ 4)!

= 211876

Из четырёх три туза выбираются единственным способом, и ещё 46 карт

46
..
остается, следовательно


Г

~

211876

~ 0 000217
^ '
v

v v v

i

Подкидной дурак
Игра никем не уважаемая. Никогда не видел, чтобы в дурака играли на
деньги. Краткое напоминание о правилах в объёме, необходимом для
понимания контекста. Играют малой колодой в 36 карт. Открывают
козыря. Козырь бьёт любую не козырную карту и меньшего козыря. Сдают
по шесть карт. Игрок ходит под соседа. Если сосед не может побить карту,
то берёт обе. Проигрывает тот, у кого на руках в конце остались карты.
Насколько известно мне, подкидной дурак воспет только в одном
произведении, впрочем, гениальном ­ Пропавшая Грамота Гоголя.
Краткое содержание предыдущих серий. Нечистая сила утащила у казака
грамоту, которую он вёз к царице. Точнее ­ шапку, в которую была зашита
грамота, которой, шапкой, казак поменялся с другим казаком, которого
утащила ведьма. Где логика? Зато спиваемо гарно! А царица у Гоголя
всегда одна ­ Екатерина Вторая. Казак с трудом, но добрался до нечистой
силы, которая диктует свои условия.
­ Ладно! ­ провизжала одна из ведьм, которую дед почел за старшую над
всеми потому, что личина у ней была чуть ли не красивее всех. ­ Шапку
отдадим тебе, только не прежде, пока сыграешь с нами три раза в дурня!
Играть в дурака ­ дело позорное.
159

Что прикажешь делать? Козаку сесть с бабами в дурня! Дед
отпираться, отпираться, наконец сел. Принесли карты, замасленные,
какими только у нас поповны гадают про женихов.
Казак два раза проигрывает, остался последний шанс.
Стал набирать карты из колоды, только мочи нет: дрянь такая лезет,
что дед и руки опустил. В колоде ни одной карты. Пошел уже так, не
глядя, простою шестеркою; ведьма приняла. «Вот тебе на! это что? Э­э,
верно, что­нибудь да не так!» Вот дед карты потихоньку под стол ­ и
перекрестил: глядь ­ у него на руках туз, король, валет козырей; а он
вместо шестерки спустил кралю
Вопрос, собственно, один ­ какова вероятность набрав шесть карт из
колоды, получить на руки четыре старших козыря? Вопрос вероятности
события во всей жизни казака не изучаем ­ какая теории вероятностей,
кругом ведьмы! Применить напрямую любимую формулу не получится,
придётся сначала подумать головой, а потом, само собой, применить метод
Монте­Карло.
Сначала всё же попробуем применить чистую формульную математику.
Сколькими способами можно извлечь четыре карты из колоды, что все они
были четырьмя старшими козырями? Правильно, одним единственным,
поскольку порядок карт нас не интересует. Сколькими способами можно
добавить к им ещё две любых карты из оставшихся 32­х? Правильно,
32 х 31 _
2

­ 496 . д

е л е н и е

н

а

д

в

символизирует безразличность порядка

а

этих двух карт.
Второй вопрос ­ сколькими способами можно получить шесть карт из
колоды в 36 карт? Имеем любимый биномиальный коэффициент :

C =
k

n

=> C

k!(n ­ k)!
Осталось поделить
n

6
3 6

=

= 1947792



6!(36 ­ 6)!

первое

на второе

и выразить

в

промилле:

P * 0.000255 * 0.0255% * 0.255%о.
Промилле ­ чтобы выглядело солиднее. А теперь программируем и
проверяем. Все программы для проверки карточных раскладов похожи

160

друг на друга, но есть и специфика ­ никакой цены у карт нет, и вопрос
наш очень простой ­ есть валет, дама, туз, король одновременно, или нет.
var
D
trump
numOfHighestTrumps
numOfSuxx
card
i,k

TDeck36;
TSuit;
integer;
integer;
TCard;
integer;

begin
trump:=diamonds;
numOfSuxx:=0;
f o r i : = 1 t o 1 0 0 0 0 0 0 0 0 do b e g i n
D:=TDeck36.Create;
numOfHighestTrumps:=0;
f o r k : = 1 t o 6 do b e g i n
D.RandomCardWithoutReturn(card);
i f ( c a r d . s u i t = t r u m p ) and
( ( c a r d . r a n k = J a c k ) o r ( c a r d . r a n k = Queen) o r
( c a r d . r a n k = K i n g ) o r ( c a r d . r a n k = Ace) )
then numOfHighestTrumps:=numOfHighestTrumps + 1;
end;
if

numOfHighestTrumps = 4
t h e n numOfSuxx:=numOfSuxx + 1 ;

D.Free;
end;
ShowMessage('numOfSuxx = ' +

IntToStr(numOfSuxx));

end;

В общем, вопросов нет. Единственное, обратите внимание на количество
выполнений цикла. Всё так и было, так я программу и запустил. В связи с
этим, настоятельно рекомендую прикрутить к форме компонент типа
TLabel ­ просто текст и по условию ( i mod 1 0 0 0 0 0 0 ) = 0 выводить значение
I. А то возникает некоторая тревога ­ как там программа, жива ли.
Понятно, что запускать для тысячи итераций смысла никакого нет, да и для
десяти тысяч немного. Начнём со ста тысяч.

161

Число
испытаний
100000

Козырные валет, дама, о /
700
король, туз
23
0.230%о

1000000

252

0.252%

10000000

2509

0.251°/%

100000000

25909

0.259°/%

Сходится медленно, но всё­таки сходится, и сходится туда, куда надо. Что
касается вероятности события, то, заменяя проценты и промилле на
понятный язык, такая карта случается один раз на примерно 4000 раздач,
или, очень грубо, один раз на 200­500 игр. Мне так кажется. Ничего
особенного, даже ведьму привлекать не надо.

162

Глава 7
Теория вероятностей.
Скучные и важные понятия в виде конспекта
Пояснение
Я сначала хотел, чтобы эта глава была очень длинной. Потом решил
перенести её в виде конспекта в Приложение. Потом решил написать
коротко, но здесь. Но конспективно.
Распределение. Что это такое
В этой главе мы займёмся программированием. Но сначала снова возьмём
в руки нашу любимую колоду из 36 карт. Вытащим карту. Потом другую.
Третью. В руки попался любимый пиковый туз, пиковая дама и туз бубён
Какова вероятность их извлечения, точнее извлечения одной из трёх?
Правильно, 1/36. А почему мы не спрашиваем, о какой именно карте идет
речь? А потому, что вероятности для всех карт равны. Соответственно,
такое распределение называется равномерным. Заметьте, я не дал
определения тому, что такое распределение, мне кажется, что это понятие
скорее относится к интуитивно понятным ­ или интуитивно непонятным.
А что может быть понятнее картинки? Так нарисуем картинку! Точнее,
график. По горизонтали отложим карты ­ от шестёрки пик до туза бубен, я
не знаю, почему они именно в таком порядке идут в колоде. По вертикали,
сколько именно раз каждая карта выпал при 36 опытах (вытаскивании из
колоды) с возвращением в колоду, само собой. Потом повторим опыт при
36 опытах и при 36 . Нарисуйте графики, желательно на одном поле.
Обдумайте.
2

3

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

163

Ещё теория.
Несколько несложных, но скучных формул.
Поговорим о среднем
Сначала о грустном. Странные слова ­ среднее, средневзвешенное,
медиана, мода.
Некоторые их этих слов вам прекрасно известны,
некоторые нет. А некоторые вам известны, но вы их неправильно
понимаете. Или не понимаете вообще. Или думаете, что не понимаете, но
на самом деле понимаете... Ну то такое ­ как говорят вна Украине.
Возьмём десять небольших ­ чтобы легче считать ­случайных чисел. Для
разнообразия, программу писать не будем. Сделаем это в Excel. Чтобы
получить одно случайное число надо написать в клетку следующее:
СЛЧИС()
Число будет в диапазоне [0,1],
Чтобы число стало в диапазоне [0,10] напишем
СЛЧИС()*10
Можно, конечно и по­другому, скобки, они не просто так. И, наконец,
чтобы случайное число стало целым, завершим композицию:
ОКРУГЛ(СЛЧИС()*10;0)
Теперь, ухватив клетку за правый нижний угол, растянем одноклеточное
на десять клеток в ряд. Если вы думаете, что я выучил Excel Специально
для этой книги, то ошибаетесь, я его два года преподавал. И что? А ничего.
Наша кошечка сначала не любила пылесос. А потом ничего, втянулась. ©
А вы знаете, что пылесос на украино­галицийском диалекте будет
порохотяг?
При каждой новой загрузке страницы мы получим десять новых чисел. Вот
типичный, специально не подобранный, результат:
8,10,7,6,5, 10, 3, 6,9,2
164

Что с этими числами можно сделать? Посчитать среднее. Точнее ­ среднее
арифметическое. Сам термин как бы намекает, что бывает и другое
среднее, не арифметическое. Действительно, есть ещё среднее
квадратичное. Но это мы поползли по дереву эволюции вверх. А могли бы
и вверх. А могли бы и вниз. По мнению некоторых, среднее, просто
среднее считается так:
среднее =

8 +10 + 7 + 6 + 5 + 3 + 9 + 2
= 6.25

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

8 +10 + 7 + 6 + 5 +10 + 3 + 6 + 9 + 2 „ „

= 6.6

Разница есть. Следующим номером в программе, пока чисто для
кругозора, следует среднее квадратичное, оно же среднее квадратическое:
,8 +10 + . . . + 9 + 2
среднее квадратичное = A

«7.09
2

2

2

2

В быту, то есть в той, повседневной, статистике, которую употребляют в
СМИ, среднее квадратичное не употребляется. Но в теории вероятностей и
в математической статистике одним из важнейших понятий является
производное от него ­ среднее квадратичное отклонение. Об этом чуть
позже. И ещё ­ среднее квадратичное всегда больше или равно среднего
арифметического. Вдруг это знание вам пригодится.
Пошли дальше. Если вас заинтересует исконно американская статистика,
не в пересказе, а именно в оригинале, вы заметите почти полное отсутствие
понятия среднего. Никто не говорит о средней ­ average ­ зарплате.
165

Говорят о медианном ­ medium ­ доходе. Тонкая разница между зарплатой
и доходом нас тут не интересует, а вот об отличиях среднего от
медианного мы и поговорим.
Как посчитать медиану? Собственно, уметь считать для этого не
обязательно. Выпишем числа в столбик по возрастанию. Затем проведём
горизонтальную линию ровно посередине. Выглядит примерно вот так,
нарисовал как смог.
2
3
5
6
6
7
8
9
10
10
У нас чисел ровно десять, то есть число чётное, линия прошла между
пятым и шестым по порядку числом. Если бы чисел было одиннадцать,
линия перечеркнула бы шестое число сверху. Оно бы и стало медианой. В
нашем случае можете выбрать любое из двух ­ то, что сверху, или то, что
снизу. Если вы считаете себя математиком, можете взять среднее из этих
двух чисел, но это уже лишнее, и так неплохо получилось.
Заметьте ­ медиана может быть и больше среднего арифметического, и
меньше. Обдумайте.
Мода ­ это очень просто. Даже чересчур. Мода это то значение, которое в
выборке встречается чаще всего. В нашей выборке число 6 и число 10
встречаются по два раза, все остальные по одному. Они, то есть 6 и 10, и
есть моды. Две моды. Ну, бывает.
Прежде чем перейти от школьной арифметики к практически высшей
математике, я расскажу вам быль. Быль ­ значит правда, не враньё. Надо
заметить, что согласно договору, заключённому мной с Главным
166

Издателем™, я отвечаю за нарушения копирайта ­ то есть, воровать у
других авторов низзя. Всё остальное можно. Но я добрый и трусливый,
поэтому заранее отмазываюсь сам и отмазываю Главного Издателя™. Всё
далее изложенное я почерпнул из энциклопедии Microsoft Cinemania, так
что все претензии к Билу ­ или Биллу? ­ Гейтсу. Более того, далее
следующий текст представляет собой вольный пересказ матюгов Роджера
Эберта из этой самой энциклопедии. Как, вы не знаете кто это? Роджер
Эберт ­ величайший кинокритик всех времен и народов. Единственный
критик, которому заложили звезду на Аллее Славы в Голливуде, или кто
там где и кого закладывает.
Это был Disclaimer, если чё.
// Опускание Михалкова Биллом Гейтсом. Или наоборот
Гениальный исполнитель роли сэра Генри Баскервиля ­ овсянка, сэр ­
получил ещё и американского Оскара за неведомую хрень под названием
Опалённые солнцем. Дело было в Америке, где, как всем известно, среднее
арифметическое изучают в университетах в курсе математической
статистики.
Фильм номинировался в категории Лучший фильм на туземном языке, то
есть не на английском. Как определяют победителя в этой категории? То
есть, как определяли до Никиты, нашего, Михалкова? Есть члены
Киноакадемии с правом голоса, пусть тысяча штук академиков. Они
смотрят фильм и выставляют оценку ­ от единицы до десяти баллов, ноль
нельзя, ноль ­ это очень обидно, поэтому ноль нельзя. Все оценки
складывают, у кого больше, тот и победил. Просто и логично ­ до Никиты,
нашего, Михалкова, который таки, изучал в советской школе арифметику.
Схема эта замечательно работает с чисто американскими фильмами,
которые смотрят все или почти все академики. С фильмами от дикарей
возникают мелкие проблемы ­ их не смотрят. Гениальный фильм
Мумба­юмба какого­нибудь гениального режиссёра из Нигера или
Нигерии или Либерии, или, не побоюсь этого слова из Гондураса, смотрят
пятьдесят рыл и выставляют ему почти высшую оценку ­ 9. Итого он
имеет 50x9 = 450.
Что происходит дальше ­ со слов Гейтса и Эберта? Михалков устраивает
просмотр своего фильма, с икрой и шампанским. Икра ­ это caviar, в
167

смысле чёрная икра. Вся прочая икра ­ fish eggs, вот вы, персонально,
будете жрать рыбьи яйца? Так что икра была только чёрная. Насчет
блекджека и шлюх, они, Билл с Роджером, что­то не договаривают.
Являются триста голодных академиков, жрут и пьют. Разумеется, это
честные академики ­ в американском смысле слова. Сожрав и выпив, они
ставят фильму по три балла. Итого имеем 300x3 = 900. Напрягаем мозг ­
900 > 450, правда? Гондурас идёт лесом, обалдевшая Академия чешет
репу. Спартак чемпион!
Академия приглашает для консультаций профессоров из Беркли, Гарварда
и Йеля и те рассказывают им сакральное знание о среднем
арифметическом. Правила немедленно меняются. Все, в общем­то,
счастливы
// конец Взаимного Опускания
Очень коротко, потому что всё ясно
То же самое, вид сбоку и немного сложнее
Важнейшие, нет не так, ВАЖНЕЙШИЕ! понятия теории вероятностей ­
математическое ожидание и дисперсия. Покажем конкретно, то есть на
программном коде. В программном коде этом сначала заполним массив
случайными числами, потом посчитаем для них математическое
ожидание. Выглядит это, в простейшем варианте, так:
const
N = 10;
var
a
av
stroka
i
begin
RandSeed:=0;

array[1..N]
single;
string;
integer;

i : = 1 t o N do b e g i n
a[i]:=Random*100;
end;
for

av:=0;
f o r i : = 1 t o N do b e g i n
av:=av + a [ i ] ;
end;
av:=av/N;
stroka:='';

168

of single;

i : = 1 t o N do
stroka:=stroka + FloatToStrF(
ShowMessage(stroka);
for

ShowMessage(

a [ i ] , ffFixed,

'av = ' + F l o a t T o S t r F (

av, f f F i x e d ,

7,2) + '

7,2));

end;

Технические замечания. Функция Random без параметров возвращает
случайное число в диапазоне [0,1], будучи умноженным на 100, имеем
число в диапазоне [0,100]. Хорошее число ноль, на что его ни умножай,
всё равно ноль и останется. RandSeed отвечает за то, что при каждом
запуске программы случайные числа у нас будут совершенно не
случайными, а совершенно одними и теми же. Программы,
обрабатывающие случайные числа, гораздо проще отлаживать, если число
не случайные, а наоборот ­ постоянные. Вот такой парадокс.
В общем и целом, не кажется ли вам, что вы стали жертвой вульгарного
математическое
обмана? Вам пообещали некое романтическое
ожидание, а взамен выдали убогое среднее арифметическое? Увы, всё так
оно и есть.
Скучно, девочки © ВВП
Впрочем, вас должна утешить дисперсия. Начать с того, что есть
собственно дисперсия и есть среднее квадратичное отклонение. В
принципе, это абсолютно одно и то же, но их положено тонко различать.
Но сначала поглядим на результаты работы. Вот случайные числа:
0.00 3.14 86.10 20.26 27.29 67.17 31.87 16.18 37.22 42.57
Вот математическое ожидание:
Av = 33.18
Какие мысли нас посещают? Сначала мысль абсолютно левая ­ какова
вероятность встретить среди десяти случайных чисел число % с точность в
три знака? Посчитайте сами. Далее, мы заказывали числа в диапазоне
(0,100), логично было бы думать, что математическое ожидание найдётся в
районе пятидесяти, однако у нас оно оказалось сильно сдвинутым влево. А
почему? А потому, что чисел всего десять, а на такой маленькой выборке

169

может иметь место ну совершенно любое чудо. Поглядим, что происходит
при увеличении N.
Число испытаний

Математическое ожидание

10

33.18

100

48.79

1000

49.40

10000

49.81

Как видим, математическое ожидание быстро сходится, и, причём,
сходится туда, куда надо. Теперь ввернёмся к обещанным дисперсии и
средне­квадратичному отклонению. Тут нас ждёт определенный парадокс.
Ср.­кв.­отклонение всегда определяется как корень квадратный из
дисперсии, то есть ­ величина заведомо вторичная и более сложная.
Между тем, если приглядятся, всё очень и очень просто ­ среднее
квадратичное это математическое ожидание отклонения величины от её
математического ожидания. Дисперсия, в таком случае, это квадрат
отклонения величины от её математического ожидания. На пальцах:
Есть три случайных значения, пусть это будут [1,3.11].
математическое ожидание:

Считаем их

n

•Pt . Это каноническая формула из учебника Вентцель.
1=1

Поскольку у нас простейший случай ­ System.Random ­ то вся эта фигня
1 + 3 +11 _
сводится к
3
~~ . Это мы вычислили математическое ожидание.
Если немного задуматься, то нетрудно догадаться, что математическое
ожидание отклонения случайной величины от её математического
ожидания неизбежно и неуклонно стремится к нулю, что банально и
бесполезно. Поэтому для начала вводится такое понятие, как дисперсия ­
математическое ожидание квадрата отклонения случайной величины от её
математического ожидания:
5

5

D[X] =

M l

= Z (x, ~ m ) P
2

x

1

i=1

170

(1 ­ 5) + (3 ­ 5) + (11 ­ 5)
^
3
«18.66 . Это дисперсия.
2

Для нашего случая

2

2

л

о

г

г

Далее сама мадам Вонг Вентцель признаётся, что дисперсия ­ это ни о чём,
потому что размерность её отличается от размерности случайной
величины, и чтобы хоть какой­нибудь смысл вернулся, из дисперсии надо
выдернуть квадратный корень, что мы с удовольствием и делаем:
a[X]

= 4D[X]

W 18.66

* 4.319

Далее то же самое, но программно и для общего случая.
disp:=0;
f o r i : = 1 t o N do b e g i n
disp:=disp + Sqr(a[i]­av);
end;
disp:=disp/N;
msq:=Sqrt(disp);

Нарисуйте таблицу, аналогичную предыдущей , но с добавлением
дисперсии и среднего квадратичного. Посмотрите, куда сходится
математическое ожидание ­ ведь это очевидно? Почему дисперсия не
сходится к очевидному нулю? Почему она вообще никуда не сходится?
Задумайтесь.
А теперь ­ и так далее. Если среднее квадратичное отклонение является
математическим ожиданием отклонения случайной величины от её
математического ожидания, то мерещится ли вам здесь рекурсия? Нельзя
ли и дальше продолжить эту цепочку? Можно. Общее название этого
безобразия ­ центральный момент. Дисперсия ­ второй центральный
момент, а далее моменты следуют по возрастающей. И нет им конца...
Для чего всё это нужно? Пока что ­ запомните это всё.
Теперь два вопроса с ответами ­ ожидаемых от вас. Первый ­ каков
физический/повседневный
смысл
математического
ожидания
и
дисперсии. Второй ­ а нужно ли нам это?

171

Великий и ужасный метод Монте­Карло
Здесь об этом не будет. Об этом будет в моей следующей книге по методам
оптимизации. Для начала почитайте у Я.Перельмана о вычислении
значения % методом бросания иглы. Если думаете, что это банально ­
лучше из Кнута (том 2 страница 420) разложение на простые множители
случайным образом.
Немного о разном вероятностном
Далее идет то, что обычно называется Математическая
случае ­ Смесь Вероятностная.

Смесь, в данном

Сначала о зависимости и корреляции.
Что такое зависимость случайных величин? Пусть мы случайным образом
определили радиус. Потом мы вычислили площадь окружности с этим
радиусом, причем число % для формулы взяли со случайной помехой в
третьем знаке, да хотя бы даже и во втором. Здесь мы имеем классический
случай зависимых величин ­ случайная сама по себе площадь зависит от
случайного самого по себе радиуса.
Теперь возьмём опять­таки классическое исследование британских
ученых. Так вот, британские учёные однозначно доказали, что
способность к решению математических задач пропорциональна размеру
ботинок. Сюрприз? Не совсем, попросту говоря, старшеклассники решают
задачи лучше и ботинки у них побольше будут. Здесь мы имеем
корреляцию — две величины ­ математическое образовании и размер обуви
­ зависят от третьей ­ возраста.
Ещё пример. Я стараюсь не писать ­ и не пишу, да ­ о том, что относится к
узкой сфере моих узко профессиональных узко­интересов. Однако, иногда
журналисты достают. Когда они несут бред первый раз, я переношу это
спокойно. Когда сто первый, меня это раздражает. Вот только что,
пятнадцать минут назад, какой­то деятель, в очередной раз объявил, что
своим успехам в добыче сланцевой нефти США ­ или, как сейчас можно
писать, СГА ­ обязаны двум технологиям ­ гидроразрыву пласта (ГРП),
причём нетрадиционному ГРП, и горизонтальному бурению. А Россия,
типа, всё выдумала уже, но отстаёт, потому что Нет пророка в своем
отечестве.
172

ДБъ! © Лавров
Технологии эти между собой независимы. Они коррелируемы. Они
зависят от другой величины, от самой важной на свете ­ от времени. Так
вот, чисто случайно получилось, что нетрадиционный ГРП и полный цикл
технологий горизонтального бурения появились в одно десятилетие,
потому и пошли иногда в одной связке. По факту и ГРП игоризонтальное
бурение чудесно обходятся друг без друга.
Понимаю, что вам это совершенно неинтересно.
Немного разных штучек.
Вот такая загогулина, понимаешь! © Б.Н.Ельцин
Первый кандидат на загогулину от Литлвуда. Стержень в вагоне.
Стержень шарнирно прикреплен к полу железнодорожного вагона и
предоставлен самому себе. Тогда существует малая, но отличная от нуля
вероятность, что он не упадет в течение двух недель: вероятность равна
примерно
припоминаю, что мне сообщили, что гений, впервые
задавший этот вопрос, не был в состоянии ответить на него
© Литлвуд
Далее у Литлвуда следует обсуждение самых удивительных совпадений,
какие могут случиться с человеком в течение всей его жизни. При первом
чтении книги Литлвуда ­ в восьмом классе ­ мне это казалось интересным.
Сейчас всё это нисколько не удивляет и кажется банальным и унылым.
Из Мостселлера Пятьдесят вероятностных задач:
Мы все в школьные годы забавлялись, проводя на страницах книги
извилистую линию, проходящую между словами.
Вы забавлялись?
Они тупые! © Голосом Задорнова
Допустим, что мы взяли страницу энциклопедии, набранную убористым
шрифтом, содержащую 100 строк, и наугад черкнули по ней линию.
Принимая 1:5 за вероятность того, что эта линия пройдёт между словами
173

одной строки, мы найдём, что вероятность успешного прохождения линии
через всю страницу равна 1:10
70

К этой же категории вопросов относится нахождение вероятности
того, что обезьяны, барабанящие по пишущей машинке, отпечатают
Гамлета. Считая, что текст состоит из 27000 букв и пропусков и что
имеется 35 клавиш, найдём, что эта вероятность равна 1:33 .
© Мостселлер
27000

Найдите разумное объяснение, как 35 превратилось в 33 © Я
Опять из Пятьдесят задач Мостселлера. Метазадача:
При бросании 100 монет какова вероятность выпадения ровно 50 гербов?
А вот это встречается часто, везде и в разных вариациях:
Молодой человек идёт в метро и садится на поезд в любом направлении —
налево или направо. Поезд налево — к девушке. Поезд направо — к матери.
Мать жалуется на то, что он редко у неё бывает, но юноша
утверждает, что его шансы равны. Молодой человек обедал с матерью
дважды в течение 20 дней. Объясните это явление.
А здесь главное ­
формулировки:

формулировка задачи, или ­

понимание этой

Если хорда выбирается наудачу в заданном круге, то какова вероятность
того, что её длина больше радиуса круга?
Ответ ­ вероятность плавает в широчайших пределах в зависимости от
того, как вы определите термин наудачу.
Вот ещё глубочайшая мысль в простой формулировке, причём
предназначенная для самых что ни на есть ­ не то что бы маленьких ­ но
явно невзрослых:
Одиночные случайные события не имеют вероятности, они случайны
© Ю.М.Отряшенков «Юный кибернетик», М. Детская литература, 1978

174

Долго думайте. Вам надо примеров? Их есть у меня! Помните, не, конечно
не помните, замечательный стих:
Вот мост через тихую местную реку,
С которого сбросить нельзя человека,
Поскольку, по данным замеров, река
Под этим мостом чрезвычайно мелка.
А вот и Борис, что с моста сброшен в реку,
С которого сбросить нельзя человека,
Поскольку, по данным замеров, река
Под этим мостом чрезвычайно мелка.
А вот и мешок от вьетнамского риса,
Который, по слухам, надет на Бориса,
Который был сброшен с моста прямо в реку...
Это, собственно, о чём?
It was many and many a year ago,
In a kingdom by the sea...
Извините, попутал. Так и хочется иногда образованность показать. Второй
стих их Эдгара По об Аннабель Ли, первый стих из неизвестного ­ для
меня автора о Б.Н.Ельцине, который не был тогда ещё президентом.
Какова вероятность для Председателя Президиума Верховного Совета
РСФСР оказаться быть скинутым в реку? Да никакая! Не в смысле, что таких
людей в речку Вонючку не кидают ­ в смысле мы не можем её вычислить. А
то, что мы не можем вычислить, для нас не существует. Для априорного
вероятностного подхода мы не в состоянии задать исходные вероятности
величин, от которых зависит результирующие событие. Для байесовского
подхода преград нет ­ поскольку никогда такого не было, то вероятность "2
­ или кинут, или нет. Обратите внимание, что автор шедевра с нами согласен
­ С которого сбросить нельзя человека, то есть, в глубине души, он с нами
согласен ­ вероятность события равна нулю, однако ­ вот оно.
Никогда такого не было — и вот опять! © Черномырдин
175

Глава 8
Настоящая Математика ­ математический анализ
и что с ним делать
Нужен ли вообще программисту математический анализ? В плане общего
развития он нужен всем, без исключения. Другой вопрос, найдутся ли ему
конкретные, сиюминутные применения в повседневной практике
программиста. Скорее всего ­ нет. Девять шансов из десяти, что нет. Но
так и со всеми разделами математики, некоторым персонажам и
арифметика не понадобится. Теперь, вооружённые знанием теории
вероятностей, оцените вероятность того, что хоть что­то из высшей
математики пригодится. Так что приступаем.
Сначала о том, что почитать. Вот это считается классикой:
Г.М.Фихтенголъц, Курс дифференциального
в трёх томах

и интегрального

исчисления,

Мы по этому не учились, это уже тогда было чем­то легендарным и
ужасным. Некоторое время назад я всё же прочел. Фихтенгольц мне
показался нестрогим, простым и занудным ­ сначала глава об
определённых интегралах, затем глава о двойных интегралах, потом ­
сюрприз! ­ глава о тройных, и, наконец, об интегралах произвольной
размерности. Зато уже никогда не забудешь Если прочитаешь
А у нас в университетской библиотеке учебники выдавали случайным
образом. Мне достался вот такой:
С.М.Николъский, Курс математического анализа, в двух томах
Мне понравилось и пробудило интерес. Автор, кстати, только недавно
помер, в сто с чем­то лет. Высшая математика, она, вообще, того ­
продляет жизнь
А вот это древность даже на фоне Фихтенгольца, но чем древнее ­ тем
понятнее:
Акад. Н.Н.Лузин, Дифференциальное исчисление
176

Академик Н.Н.Лузин, Интегральное исчисление
Всё изложено кратко, понятно, бодро и весело. Немного отвлекает, что по
оси абсцисс у автора всегда отложено время.
Основная проблема с математическим анализом, как и со всей прочей
математикой, это ­ посмотрев на омерзительную реальность, догадаться,
какой формулой её можно описать, и обратно ­ вычитав в книжке
красивую идею найти для неё подходящий объект.
Теперь о том, что из математического анализа надо знать и понимать.
Обратите внимание, я не говорю ­ пригодится в жизни. Возможно, то что
вы будете понимать, вам не придётся применять напрямую, но благодаря
этому вы, внезапно, окажитесь в состоянии понять нечто гораздо более
сложное и полезное. Так бывает, я знаю.
Окинув взором убегающий волна за волной по оси абсцисс график синуса,
я сделал вывод. Нам надо ­ для начала ­ три вещи: ряды,
дифференцирование, определённые интегралы. А для самого начала надо
вспомнить, что такое предел.
А для самого начала ­ в этой главе повсеместно переменные типа single
заменяются на переменные типа double. В математическом анализе очень
любят точность. Ещё раз напоминаю, что не ставлю никаких проверок
деления на ноль ­ в математическом анализе это сплошь и рядом. Я имею в
виду, сплошь и рядом формулы, которые незаметно приводят к делению на
ноль.
Пределы
Предел ­ это очень просто. Предел бывает очень много у чего, как в
математике, так и вне её. К примеру, предел есть у функции. Это очень
просто. Определение, самое простое, потому что нас так учили, теперь в
ходу определения позамысловатее:

Iim f (x) = a Vs > 03 S > 0Vx :0 | f (x) ­ a |< s
0

Правда ведь, очень просто? На всякий случай, переведу на человеческий с
математического. Есть функция f(x) от одного переменного, от одного ­
177

разумеется, только для начала. К примеру, f (

х)

вопросом, чему равно

х

+

­ х + 3. Мы задаёмся
2

. У меня есть интуитивное, ни на чём не

3 )

основанное предположение, что значение этого предела равно в точности
семи. Если у вас есть интуитивное предположение, что автор глуповат, то
слова ваши обидные. Я понимаю, что это само собой очевидно, но
всё­таки, применим определение и нарисуем таблицу:
f(2­s)

2­е
1
0.1
0.01
0.001
0.0001

1

4
1.9
1.99
1.999
1.9999

6.61
6.96
6.996
6.9996

3
0.39
0.04
0.004
0.0004

По определению предела, мы должны задавать 5 и, каким­то волшебным
способом, определять для него s. Мы поступаем наоборот, то есть ­ задаём
Б и получаем для него 5, но какая разница? Результат тот же. Но зачем же
мы вообще этим занимаемся, ведь с первого взгляда ответ очевиден?
Кстати, всё у нас так идеально потому, что функция наша непрерывная,
гладкая, без разрывов и, вроде бы, ещё и кусочно­монотонная. Совсем
забыл, она ещё и аналитическая.
Теперь рассмотрим два других примера ­ один с ответом мало очевидным,
второй с ответом чуть менее очевидным, чем в первом примере.
sin( х)
X ? Это любимая задача всех авторов учебников,
Чему равен X
i m

1

замечательный предел, и вообще ­ правило Лопиталя. Эту замысловатую
задачу мы решим чуть позже, но не аналитически, а чисто программным
способом, как если бы мы этой высшей математики и вообще не знали.
А пока заметим, что вот это вот ~~^ 0 может выглядеть и вот так: х —» ос
. То есть наш x стремится к бесконечности, в этом случае к положительной,
а с тем же успехом мог бы и к отрицательной. Надеюсь, вы знаете точный
математический смысл слов стремится к бесконечности. А если не
знаете, то интуитивно догадываетесь. В математике это почти одно и то
же, хотя она и самая строгая наука. Рассмотрим простенький предел
х

х

178

j.™ 2

+ 1

x

. Если вам опять всё интуитивно понятно, то попрошу немного

помолчать. Тройка наверху и квадрат в показателе внизу добавлены, чтобы
вас запутать. Единица служит той же цели.
В этом случае, в отличие от предыдущего предела, который

1

i

m

(

х

+ ,и
3 )

для которого можно просто взять значение в точке предела, эта самая
точка бесконечно удаляется и всегда за горизонтом. Нарисуем таблицу,
только немного другую, потому что s у нас нет, и в последнем столбце,
который 5, будем считать что это отклонение от интуитивно ожидаемого
значения ­ а вы какое значение ожидаете?
x

f(x)
4
1.03
1.003
1.0003
1.00003

1
10
100
1000
10000

5
3
0.03
0.003
0.0003
0.00003

Идея понятна, то есть понятно ­ к какому пределу стремится значение
нашей функции по мере того, как её аргумент стремится к своему пределу.
Обратите внимание, что предел совершенно не зависит от того, какая
константа написана у дроби в числителе. Ещё обратите внимание, что
предел точно так же не зависит от того, что написано у икса в показателе,
если показатель больше единицы. А если меньше? Обдумайте.

,.

1

Ещё один элементарный пример, но другого рода:
_ 3 .Проблема здесь
в том, что функция имеет разрыв при x=3. Это плохой разрыв ­ бесконечный,
он же неустранимый. Потому что функция по мере приближения к точке
разрыва стремится к бесконечности и починить это невозможно:
n

m

х

s
1
0.1
0.01
0.001

1

x­3+s
1
0.1
0.01
0.001

f(x­3­s)
10
100
1000
179

5
как­то
всё
пошло
вразнос

Раньше у нас был конечный предел в бесконечности, теперь бесконечное
значение предела при конечном значении x.
x ­ 4
2 Опять функция
2

Тоже простой предел, но с хорошим концом: X

i m
2

x

+

имеет разрыв, теперь при x=­2. Это хороший, безобидный разрыв ­
устранимый. То есть, мы можем просто для себя решить, что f(­2) чему­то
равно, и разрыв исчезнет. Чему именно равно? Пределу в точке. Рисуем и
заполняем таблицу:
s

­2­s
3
­

1
0.1
0.01
0.001
0.0001

­
­2.1
­2.01
­2.001
­2.0001

f(­2­s)
5

1
0.1
0.01
0.001

­4.1
­4.01
­4.001
и так ясно

Предел явно стремится к минус четырём. Возможно, у вас возникла мысль
провести следующую нехитрую алгебраическую операцию, требующую
начальных познаний в школьной алгебре:
,. X ­ 4 ,. (x + 2)(x ­ 2)
,
lim
= hm
= lim (x ­ 2) = ­ 4

x+2

^­ x + 2
2

x

2

x

2

x

2

Если такая мысль возникла, то академик Н.Н.Лузин, умерший в 1950­м
году с вами совершенно согласен, но с тех пор наука шагнула далеко
вперёд. Теперь та функция, что до сокращения, и та, что после ­ это совсем
разные функции.
Лузин по­простому, рабоче­крестьянским способом классифицирует
функции и их пределы так ­ по отношению к пределу в конкретной точке:
­ хорошие, гладкие, непрерывные

.

3 x

~

1
lim
^ _3
,. . 1

стремящиеся в бесконечность
бестолково колеблющиеся

l l i n

x

l

i

x—

m s i n

*

180

3

3 x

^
x—3

5 x

+

1

­ с разрывом

академику,
точки

и

предел

с двух

является

тот ж е

с а м ы й

x = 3 о н стремится

сторон

разный
l l i n

предел

в п о л о ж и т е л ь н у ю

.

x

­

_3

примером,

, с одной

бесконечность,

­

с

у с т р а н и м ы м

П р и м е р

испорченная

у ж е

разрывом
б ы л

формула

и

­

выше.

то, что
Л у з и н

настоятельно

интересует

ласково

от
в

деле

академика

называет

советует

стороны

а с другой ­

отрицательную. Часто встречается в физике и в и н ж е н е р н о м

всего.

согласно

это

формулу

больше
явление

немедленно

починить.

Однако, рисовать т а б л и ц ы и считать на калькуляторе грустно и

Но работать без подручных,
Может грустно, а может скучно. ©

В ы с о ц к и й

Пусть,

свое

наконец,

к о м п ь ю т е р

займется

п р я м о й

­

работой

м о и х текстов, а расчётами. Сначала, сами себе, сформулируем
задание. О н о м н е видится примерно

Н а

вход

н а ш е й

процедуры

печально.

не

набором

техническое

так:

поступает

функция.

математическом, так и в программистском.

Ф у н к ц и я

в

смысле

как

Т а к ж е поступает величина

Н а ш а процедура д о л ж н а попытаться выяснить, есть л и у ф у н к ц и и предел
этой

точке.

значения

В о з м о ж н ы е

предела

функция сходится

­

ответы

предел

в

есть,

абсолютно.

особый случай, когда

­

стиле

предел

акад.

Лузина

бесконечен,

­

кроме

предел

Процедура д о л ж н а обработать

X.
0

в

самого
есть,

правильно

но
и

существует

l im f (x)
. Разумеется, нельзя
без объявления процедурного

Вот интерфейсная
unit

siLimit;

обойтись

типа.

часть:

//

23.06.2017

//

24.06.2017

{
interface
type
TFunction = function (
x : double) : double;
TLimType = ( l t O k , l t I n f , l t O s c , l t N o n e ) ;
TLimSide = ( l s L e f t , l s R i g h t ) ;
procedure F i n d L i m i t (
F
: TFunction;
x0
: double;

181

}

var
var

limSide
limType
limit

TLimSide;
TLimType;
double);

Комментарии. Объявление функционального типа вам должно быть
понятно. TLlmType ­ тип предела или тип его отсутствия ­ надо ещё
немного обдумать и, возможно, расширить. TLlmSlde ­ безусловно,
правильная, настоящая программа поиска предела должна его искать
везде. Но за деньги. У нас программа бесплатная и игрушечная. Поскольку
есть совершенно строгие математические понятии предел слева и предел
справа, то мы и попросим пользователя нашего продукта их указать. В чём
смысл? Если мы задаём X = 1 0 и llmSlde = lsLeft, то поиск предела
начнётся от x « 9 и будет идти слева направо, при x возрастающем. А если
наоборот ­ то наоборот, от « 11.
0

Обратите внимание ­ наша процедура не ищет предел, для этого у неё
мозгов нет. Она всего лишь проверяет, есть предел в точке x , и если есть ­
чему он равен.
0

Первым делом пишем тест. А перед тем, как написать тест, напишем
незатейливую функция. На практике таких незатейливых функций
приходится писать часто и много.
function LimTypeToStr(
sLimType : TLimType) : s t r i n g ;
begin
i f sLimType = l t O k
then result:='Ok'
else
i f sLimType = l t I n f then r e s u l t : = ' I n f ' e l s e
i f sLimType = l t O s c then
result:='Osc'
else result:='None';
end;

Назначение функции просто и очевидно ­ она переводит в строковый тип
имя переменной нашего специфического типа. Это нужно чаще, чем
кажется.
Подумайте о применении вышесказанного к разрывным функциям разных
типов. Я сам не могу, объем книги конечен.
Вы следите за моими мыслями? Вы следите, я сам не могу
© Не помню откуда

182

Ряды в теории
Что такое ряд? Очень просто. Ряд, это последовательность чисел.
Последовательность может быть конечной ­ 1, 0, 9, 13, 3.11419, 7.40 ­ но
такая последовательность никому не интересна. Правильные пацаны
исследуют только бесконечные ряды. А ещё более правильные только
сходящиеся, но об этом позже.
Числа могут быть натуральными:
1,2,3,4,5...
Числа могут быть рациональными:

11 1 1 1
2 3 4 5
,

,

,

,

, . . .

Этот ряд называется гармонический. Или лучше вот так, но числа тоже
рациональные:
1
,

1 1 1 J_
2 4 8 16
,

,

,

. .

Это частный случай степенного
ряда. Иногда его называют
геометрический ряд. А чем он лучше? Я скоро объясню. Числа могут, само
собой, быть иррациональными:
1

1

1

1

1 1

1
1
1
1
(V2) '(Vs) ' (VT) '(л/5)
2

3

4

7

Ряды могут состоять и из трансцендентных чисел, но нам это не интересно.
С высоты нашего орлиного программистского полёта, что рациональные,
что иррациональные, что трансцендентные ­ одна фигня. Есть размер
машинного слова и за его пределы никакая математика не вылезет.
Занудства ради, ряд может состоять из комплексных чисел, но об этом я
вам ничего не скажу. Не потому, что это не важно ­ это очень важно. Не
скажу по той простой причине, что у нас не было курса Теории Функций
Комплексного Переменного. Ну не было, ну и всё...
183

А вот такие ряды называются знакопеременными, почему ­ понятно
каждому и сразу:
+1,­1,+1,­1,+1...

1 _ 1 1 ­ 1 ­L
8 16
,

,

..

Ряды обычно записывают не по­простому, то есть как мы записали, а,
задавая член ряда номер N как функцию от N. Ранние примеры рядов будут
выглядеть так:
1,2,3,4,5... О a = n
n

1 1 1 1
1
1, , , , < = > a
2 3 4 5
n
1 1 1 1
1
1 , , , , — . . . a =—
2 4 8 16



­

­



n

n



­

­

n

n

n

1

1

J _ J_ J_ J_

1

J_

V2 V3 V4 V5 ^
4n
1
_ L _ L _ L
( V 2 ) (V3) ( V 4 " ) ~
+1,­1,+1,­1,+1... » an = (­1) ­
, 1 1 1 1
(­1)
1 ,

,

,

,

2,

,

. . .

a

n

=

3,

4

n

_ L _L
° ~ (Vny ~ ^
n

n

1

n4

1,—,—,—, —... a = ­—­—
2 4

8 16

n

n

2

n

Что, вообще, интересного может случиться с рядом? Функция интересна
сама по себе, как объективная реальность, как записанный буковками
физический процесс. То, что у функции есть предел ­ мелкая деталь,
добавляющая некоторый дополнительный интерес. А ряд?
Разумеется, мы можем рассматривать каждый член ряда как функцию от
целочисленного аргумента ­ его номера. Немедленно после такой
формулировки возникает вполне естественный вопрос ­ а есть ли у этой

184

функции предел? Можно применить этот вопрос к ранее приведённым в
качестве примера рядам:

1,2,3,4,5... ­ предел бесконечен, что равносильно тому, что предела нет.
11 1 1 1
2 3 4 5' ­ P ^ Р
нулю.
1 1 1 J_
°.
/4816' ­
n

e

1

en

а в е н

а н а л о г и ч н

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

тот же. Со

+1,­1,+1,­1,+1.­ ­ предела нет, даже бесконечного
1

24
,

8 16

,

,

­P ^ Р

.

n

e

en

а в е н

нулю.

По большому счёту, никому это всё не интересно, почти. По­настоящему
интересно, сходится ли сумма ряда. Почему ­ увидите позже, а пока
объясню, что это такое ­ сумма ряда. Интуитивно это понятно, попытаюсь
рассказать чуть более формально.
Если есть ряд a , то сумма первых, к примеру,
n

пяти членов ряда

5

обозначается так Z n . Для конкретного ряда, например гармонического,
a

и­1

5

это запишется Z J

n

и будет равно

1 +

2

+

3 4
+

+

5 ~~ "60 ~~

60

Это называется частичная сумма ряда и, опять­таки, никому это не
интересно. А интересна только просто сумма ряда:
да

к
S

~

n . Другое, краткое, обозначение Z n . Для многократно

a

a

да 1
помянутого добрым словом гармонического ряда это будет Z ~
Поскольку в записи присутствует предел, немедленно возникает вопрос ­
существует он, предел, или нет. В первом случае говорят, что ряд
185

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

S

к—
ко

1a

1

. Впрочем,

про это можно немедленно забыть.
Чуть выше мы смотрели на ряд и пытались догадаться, к чему стремится
его член в бесконечности ­ звучит­то как! Так вот, необходимым условием
сходимости ряда является стремление его члена к нулю. Необходимым, но
не достаточным, если вы понимаете, о чём я говорю. По простому. Вот у
этих рядов член явно к нулю не стремится:
1,2,3,4,5.

+ 1,­1,+1,­1,+Т..
Простой и однозначный вывод ­ эти ряды расходятся. А у следующих
рядов, наоборот, стремится:
1
,

1
,

1 1 1 1
2 3 4 5"
1 1 1J_
2 4 8 16"
,

,

,

,

,

,

.

.

Вывод? Никакого. Может сходятся, а может и нет.
В предыдущем разделе мы написали не очень красивую программу для
работы с пределами функции. Некрасивость заключается в том, что на
вход программы надо задать точку, в которой мы предполагаем предел, а
программа проверяет, есть ли предел и чему он равен. С рядами проще ­ в
какой точке искать предел мы точно знаем ­ в бесконечности.
Поэтому сейчас самое время написать программу которая определяет,
сходится ли ряд, и чему равна его сумма. В отличие от программы про
предел функции, которая была чисто учебной, эта ­ при необходимости ­
может иметь вполне практическое применение.
Вот интерфейс:
186

unit

siSeries;

//

21.06.2011

//

21.06.2011

{
interface
type
num : i n t e g e r )
TTermOfSeries = f u n c t i o n(
TSeriesType = ( stConvergent, s t D i v e r g e n t ) ;
procedure

SumOfSeries(

var
var
var
function SeriesTypeToStr(

F
sType
sum
howMany
sType

:
:
:
:
:

: double;

TTermOfSeries;
TSeriesType;
double;
integer);
TSeriesType) : string;

Параметр HowMany не является жизненно необходимым, но служит лишь
удовлетворению моего любопытства ­ он сообщает, сколько членов ряда
мы успели просуммировать. Функцию SeriesTypeToStr любезный
читатель, без сомнения, способен написать собственными умелыми
ручонками.
Алгоритм работы процедуры мне видится простым, незатейливым и
общепонятным. Мы вычисляем значение частичной суммы ряда для
первых шестнадцати членов. Почему шестнадцати ? ­ мне так кажется.
Одновременно вычисляем разности между двумя частичными суммами.
Если разности не убывают ­ ряд расходится, всё пропало. Если убывают ­
дожидаемся, пока разность между двумя соседними суммами станет
меньше 0.001. Почему 0.001? Потому что я живу в реальном мире, а в
реальном мире, если две какие­то величины, измеренные в любых
единицах измерения, отличаются между собой меньше чем на 0.001, то
величины считаются равными.
Прежде чем что­то программировать, надо написать тест. А прежде чем
написать тест, надо знать правильный ответ. Причём правильный ответ
надо знать до написания теста. В нашем случае мы должны взять для
опытов как минимум один ряд, который сходится и один, который не
сходится.
1

1 1 1 J_

У
В качестве сходящегося берём степенной ряд 2 4 8 1 6 " " ^
сходится? А это с первого взгляда видно. А чему равна сумма? А сумма
равна двум. В роли плохого, расходящегося ряда выступает
,

1 1 1 1
гармонический ряд 2 3 4 ­ 5
.
1
1,

,

,

,

.

с

187

п е

Р

в о г о

,

,

в з г л я

п о ч е м

Д

а

в с е

о

не так

н

очевидно, но тоже всё очень просто. Утверждение, что ряд расходится,
эквивалентно тому, что сумма ряда превышает любое конечное число.
Кстати, многократно помянутый академик Лузин строго замечает, что
говорить конечное число совершенно неправильно ­ все числа конечные.
Далее ход мысли такой. Если есть ряд a и есть ряд b и для каждого n
b . В
завершение этого раздела расширение процедуры вычисления численного
приближения производной для случая функции двух переменных.
F

F

8

Сначала необходимо объявить функцию от двух переменных как тип, и
нашу конкретную хорошую функции в соответствии с этим объявлением:
TFunction2

= function (

x,y : double)

: double;

f u n c t i o n GoodFunc(
x,y : double) : double;
begin
r e s u l t : = 3 * x * x + y * y ­ 1 8 * x ­ 8*y + 36;
end;

А вот и сама процедура численного дифференцирования:
procedure D i f f 2 (

F2
x0,yC
d
var dx,dy

TFunction2;
double;
double;
double);

begin
d x : = ( F 2 ( x C + d , y C ) ­ F 2 ( x C , y C ) ) / d;
d y : = ( F 2 ( x C , y C + d ) ­ F 2 ( x C , y C ) ) / d;
end;

Что с ней делать дальше? Очевидным образом, расширить на функцию от
произвольного числа переменных. Или почти произвольного числа.
Следующим шагом ­ сейчас мы задаём точность по переменным ­ то есть
F(x + ё) ­ F(x) . Хотелось бы задавать точность по значению частной
производной, то есть считать не сразу, а в цикле, постепенными

IdLЛ JdL)

< s Точность
приближениями и останавливаться когда " dx Jn { dx ' я + 1
должна подбираться по каждой переменной независимо. И, само собой,
необходимо расширить процедуру на производные высших порядков, в
том числе и смешанные.
Ряды. Откуда всё­таки они берутся
Сначала байка с разоблачением.

209

/ / Байка про махновцев и ряд Макларена с разоблачением
Каноническая версия байки
Во время гражданской войны будущий лауреат Нобелевской премии по
физике Игорь Тамм попал в плен к одной из банд Махно. Его отвели к
атаману ­ «бородатому мужику в высокой меховой шапке, у которого на
груди сходились крест­накрест пулеметные ленты, а на поясе болталась
пара ручных гранат».
— Сукин ты сын, коммунистический агитатор, ты зачем подрываешь
мать­Украину? Будем тебя убивать.
— Вовсе нет, — ответил Тамм. ­ Я профессор Одесского университета и
приехал сюда добыть хоть немного еды.
— Брехня! ­ воскликнул атаман. ­ Какой ты профессор?
— Я преподаю математику.
— Математику? ­ переспросил атаман. ­ Тогда найди мне оценку
приближения ряда Маклорена первыми n членами. Решишь ­ выйдешь на
свободу, нет ­ расстреляю.
Тамм не мог поверить своим ушам: задача относилась к довольно узкой
области высшей математики. С дрожащими руками и под дулом
винтовки он сумел­таки вывести решение и показал его атаману.
— Верно! ­ произнес атаман. ­ Теперь я вижу, что ты и в правду
профессор. Ну что ж, ступай домой.
Тамм так никогда и не узнал фамилию атамана
Уолтер Гратцер "Эврики и эйфории "
В чём разоблачение? Ряд Макларена изучается на первом году не то, что
математического факультета в курсе математического анализа, но и
любого технического факультета в курсе высшей математики. Кстати, вы
помните ­ на случай встречи с махновцами ­что у математиков не бывает
курса высшей математики?
В альтернативной версии байки Тамм ничего вывести не смог, на что
махновец ему сказал ­ да ладно мужик, я и сам эти формулы давно забыл.
И вообще, всё было совсем не так.
// конец Байки с разоблачением
А теперь ­ возьмёмся за дело. Сначала ­ функциональный ряд. Это очень
просто, это такой же ряд, как и ряды раньше, только членами его являются
не числа, а функции. В самом общем виде ­ вот так:
210

fi(x), U x), /3( x), / ( x), f5(x)...
4

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

f

и 3 это совсем разные функции, а
f

2

вот x как раз наоборот ­ везде один и тот же. Теперь добавим конкретики.
Конкретика добавляется в два этапа. Сначала надо задать функции n
f

( x )

,

например, так:

_L _L _L J_ _L
•Л'

•Л'

•Л'

•Л'

f

(

)

­_L
щЛ'

•Л'

Вторым шагом надо задать конкретный x и посмотреть, что получится.
Задаём x=2 и получаем хорошо уже знакомый геометрически ряд:

11 1 1J_
2 4 8 16
,

,

,

,

. .

Задаём x=0.5 и получаем вот такое:

_J
1 1
1
1_ _ _± 1
1_ _J_ _
0.5 0.5 0.5 0.5 0.5 " 0.5 0.25 0.125 0.0625 ^ "
1,

0,

2,

4. . .

3,

,

,

,

,

,

,

1
, ,

2

4

8

1

6

...

Первый ряд, как мы уже знаем, сходится. Второй ряд, очевидным образом,
расходится. Как меланхолично замечает в связи с этим академик Лузин ­
расходящиеся ряды вообще никому не нужны. Особенно программистам.
А теперь о главном, сосредоточьтесь. Вот это степенной ряд:
a + O x + a x + a x + a x +... f (x) = a x ~
2

0

1

3

2

3

4

n

4

]

n1

На первый взгляд, ряд как ряд. На второй тоже. Но Макларен, или
Маклорен доказал, что любую функцию можно представить в виде такого
ряда. Тут я загнул немного, не совсем любую функцию ­ но любую
хорошую функцию. Причём делается это просто, понятно и доступно.
Даже я всё понял с почти первого раза.
Итак, у нас есть хорошая функция f (x) . Соответственно, f (x) её первая
производная,
Тогда имеем

f

"

( x )

вторая производная, а
211

f

( n )

производная порядка n.

/ /(O) = O
/'(x) = sin'(x) = cos(x) => cos(O) = 1
/"(x) = cos'(x) = ­sin(x) => ­sin(O) = O
/"'(x) = ( ­ sin x)'(x) = ­ cos(x) => ­ cos(O) = ­ 1
И так далее, я устал. Видим, что члены с нечётными номерами просто
исчезают, а остаётся вот это:
3

x

x

5

7

x

x

sin( x) =

1
h ...
1! 3!
5!
7!
Как видите, ряд Макларена ­ это очень просто. Очень просто это ещё и
потому, что ряд Макларена является частным случаем ряда Тейлора,
который, впрочем, ­ тоже очень простой. Действительно сложным здесь
является решение вопроса ­ мы действительно получили ценное
разложение функции в ряд? По этой технологии можно получить и
расходящийся ряд и, что ещё хуже, ряд сходящийся, но не туда.
Здесь должен быть рассказ о ряде Тейлора, ряде Фурье, и, само собой, о
быстром преобразовании Фурье, но я решил оставить это до следующей
книги.
Неопределённые интегралы
А что такое интеграл вообще? Впрочем, это не главное. Только что мы
изучили дифференцирование. Интегрирование ­ это то же самое, только
наоборот. Вычитание ­ это сложение наоборот. Деление ­ это умножение
наоборот. Интегрирование ­ дифференцирование наоборот.
(x ­ x + 99)' = 3x
3

2

­1
212

(Зх ­ \)dx = х ­ х + С
2

3

Загадочная буква С как бы намекает, что конкретное число 99 сгорело в
пламени дифференцирования. И восстановить его оттуда никаким
способом невозможно. Впрочем, буковка эта имеет сугубо ритуальный
характер и далее сгорает снова. Называется эта буковка постоянная
интегрирования. Забудьте. Далее о важном, важного много.
Если у нас есть функция, её всегда можно продифференцировать, то есть ­
взять от неё производную. Да, я в курсе, что почётный гражданин
Украины, уроженец исконно­украинского города Лемберг, он же Львов, по
имени Захер Мазох однажды изобрёл мазохизм. Его математические
последователи не отставали и придумали много интересных и разных
функций, от которых производные не очень берутся. Но вот вы сами
попробуйте придумать функцию без производной. Причём, сто очень
важно, взятие производной не требует гигантских умственных усилий ­
оно требует хорошей памяти и аккуратности ­ то есть, легко становится
алгоритмом.
С интегралами всё не так. Взятие интеграла ­ это Искусство, именно так, с
большой буквы. Вот что говорит академик Лузин:
Интегральное исчисление предлагает ряд целесообразных
приёмов,
достаточных для довольно многих случаев. Но учащийся не должен
заблуждаться относительно силы этих приёмов: приёмы эти лишь
систематизируют и приводят в некоторый порядок первоначальный
подход при помощи непосредственного нащупывания и догадки, и ничего
более.
По простому ­ бывают интегралы, которые берутся легко. Пример вы
видели выше. Бывают интегралы, которые берутся только через очень
сложно:
x dx

3a x 2b x

3,2

J

(a ­ b x )
2

2

2

2

l,2

2

3

/2

b (a ­ b x )
4

2

2

3a
2b

a + bx '
11

5

ln

Как меланхолично замечает Литлвуд:

213

a

­bx

v:

Математический текст такого стиля (вдохновлённый,
дьяволом) не может не содержать опечаток.

несомненно,

Если вы думаете, что я взял этот интеграл сам, то мне даже и странно, что
вы могли обо мне так подумать. То есть, взять­то я его взял, но взял из
книги, в которой он числится под номером 187.23, а книга называется
Г.Б.Двайт «Таблицы интегралов и другие математические
М.Наука, 1978

формулы»

Ещё бывают интегралы, которые не берутся вообще, при этом выглядят
вполне безобидно:
1

ln x

Что важно, если интеграл не берётся, это, конечно печаль ­ для
математиков. Программисты этого даже и не заметят.
Так вот, всё что только что было выше написано об интегралах, написано о
тех интегралах, которые называются неопределённые
интегралы.
Запомните вот такое обозначение:
f (x)dx = F (x) + С

J

Это не теорема, не формула, не определение ­ это именно обозначение.
Если есть функция f (x) , но неопределённый интеграл от неё ­ тоже
функция, но совсем­совсем другая, и мы обозначим её F(x). Обратите
внимание на вечную постоянную интегрирования С и немедленно
забудьте.
Никаких программ в этом разделе нет. Какие программы? Там академики с
профессорами не могут интеграл взять. А мы тут со своими
программами...
Определённые интегралы
Определённые интегралы ­ это вовсе не неопределенные интегралы,
которые вдруг определили. Начнём с то го, что неопределённый интеграл ­
функция, определённый интеграл ­ число. Хотя выглядят они очень
похоже ­ вот, слева неопределённый, справа ­ наоборот:

214

J x dx
1

x dx
2

2

0

Что такое определённый интеграл? Сначала с точки зрения физического
смысла, он же геометрический. Для опытов возьмём функцию, которая не
очень хорошо выглядит, но от которой, однако, очень легко взять
интеграл:
f ( x ) = x ­ 40x + 632x ­ 5024x + 21072x ­ 43776+34560
6

5

4

3

2

[ f (x)dx = ­ x
xx +
x ­1256xx + 7024x ­ 21888x + 34560x
7
3
5
Вы проверили? Я нигде не ошибся? Обозначим это безобразие, как и
положено, через F (x). График исходной функции не так уж и ужасен.
1

5

3

2

J

Очень может быть, что функция эта имеет некоторый физический смысл ­
или будет иметь, если мы захотим. Обычно предполагается, что по оси x
отложено время, а по оси y зависимая от времени величина, обычно
скорость. А что тогда означает площадь фигуры между осью абсцисс и
кривой, как сверху, так и снизу? Вот картинка:

215

Это просто понять, если представить себе этот график для скорости,
неизменной от времени, то есть f(x)= const . Тогда заштрихованная
область принимает форму прямоугольника, по горизонтали ­ время, по
вертикали ­ скорость, произведение ­ времяхскорость = пройденный путь.
В нашем случае всё точно так же, только скорость переменная. А какой
физический смысл у областей снизу оси абсцисс? Если скорость ниже оси,
значит она отрицательная, значит едем назад. Суммарная площадь
областей сверху ­ сколько проехали вперёд, снизу ­ сколько проехали
назад. Если вычесть из первого второе, получим итоговое перемещение,
опять­таки со знаком. Простой вопрос ­ а как посчитать площадь? Сначала
почти строгое математическое обоснование. Для наглядности считать
будем на интервале [6,10] который весь сверху, и для той же наглядности
немного укрупним масштаб. Обратите внимание ­ пока что
программистам и математикам по пути.

216

Теперь по шагам, как программисты, но, в конкретном данном случае, и
как математики тоже. Что будет, если мы проведём из точки (6,0)
вертикальную линию до пересечения с кривой? Ничего не будет, кривая
уже с нами. Проведём вертикальную линию из точки (7,0). Это уже имеет
смысл.
Из той точки где эта вертикальная прямая пересеклась с кривой, проведём
горизонтальную прямую, до пересечения с вертикальной линией,
проведённой из точки (8,0) до пересечения с кривой. И так далее.
Непонятно? Сейчас нарисую.

Можно ли считать, что площадь заштрихованных прямоугольников близка
площади под кривой? Вряд ли. Уменьшаем шаг по оси x вдвое.

217

Уже правдоподобнее. Продолжаем разговор © Карлсон. Что произойдёт с
общей площадью прямоугольников, когда они будут становиться всё уже и
уже, и их будет всё больше и больше? Интуитивно кажется, что площадь
их в конце концов сольётся с площадью под кривой.
Вот как­то так, через хитрую задницу, оно и работает © Билл Гейтс о
Windows 7
Интуиция нас не обманывает. Математики в любом учебнике легко и
просто доказывают, что П ^ ! ^

f

( X i )

'^

x

~

S a b

. Здесь

S

a b та самая

b

площадь, которую мы ищем, она же \

f

( x ) d x

, ^x

i

­ основание

a

прямоугольник, п ­ количество прямоугольников. С этого места дороги
программистов и математиков расходятся. Сначала о том, как поступают
настоящие математики.
Определённые интегралы. Что делают математики
Вы ещё помните, что взятие неопределённого интеграла ­ это искусство.
Так вот, определение предела суммы, которая вверху, ­ тоже искусство. А
теперь длинная, но умная цитата из академика Лузина:
До сих пор мы рассматривали интегрирование, как процесс, обратный
дифференцированию. Но это является лишь только одной стороной дела.
Другой стороной и притом несравненно более важной для бесчисленных
применений интегрального исчисления к геометрии, механике и, вообще,
218

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

настоящие

математики

переобуваются

доказывают, что если у нас есть

f

( x )

и j"

в
f ( x ) d x

воздухе
=

F

( x )

и

легко

+С ,

х 0

b

\

f

( x ) d x

_

F

(

b

)

~~

F

(

a

)

. Шутки, смех, аплодисменты. Обратите внимание,

a

что в процессе
уничтожаются.

вычитания

постоянные

интегрирования

взаимно

Всё очень просто. Для иллюстрации берём не только хорошую, но и
простую функцию. Вот функция ­
= yf
заштриховано то, что мы хотим посчитать:
f

219

( x )

2(x



2)2

, вот её график,

Имеем функцию: f (x) ­л/2~(x ­ 2) ­ V 2 ( x ­ 4x + 4), далее получаем
2

первообразную
больше

i

J

f

( x ) d x =Л

не пишу

/ 3~ ~~
2(_

постоянную

2

x

2

+

4

x

) =

F

(

x

)

. Ничего, что я

интегрирования

С? Теперь

итог:

[ f (x)dx = F(4) ­ F(3) = V 2 ­ « 3.300
3

Как видите, всё легко и просто . Проблема в том, что не всегда легко и
просто производить вычисления, даже с помощь калькулятора. Помните
нашу зигзагообразную, не помню какой степени функцию вверху. Для
которой я трудолюбиво рисовал прямоугольники? Пересмотрите
картинку. Интеграл от этой функции легко берётся, но считать его пределы
вручную немного трудно. Посчитаем всё­таки и его, для дальнейших
опытов. Обозначим обе функции (хорошую и не очень хорошую)
соответственно:
goodf (x) = V2( x ­ 2)
badf(x) = x ­40x
6

5

2

+ 632x ­5024x + 21072x ­43776 + 34560
4

3

2

Хорошую ­ goodf ­ мы только что посчитали своими собственными
руками. На плохую ­ badf ­ меня не хватило, я запустил Maple. Результат
j
6

b a d f

( x ) d x

=

*

2 0 6 7 . 5 0

. з

а п о м н и м

э т о т

результат, чуть дальше

мы получим его безо всякой высшей математики, чисто программным
способом.
А ещё там вверху был какой­то противный интеграл из справочника, мне
он сразу не понравился ­ взять бы я его не смог. А ещё, как напоминает нам
академик Лузин, бывают интегралы, которые вообще не берутся. При этом,
как заботливо разъясняет академик, если неопределённый интеграл не
берётся, определённый интеграл никуда не делся ­ вот же она, площадь
над кривой, или под. А у программистов всё просто.
Здесь я, как математик не чистый, а как чисто прикладной, должен
заметить, что в реальности нашей вселенной входные подинтегральные
данные проходят скорее по ведомству теории вероятностей.
220

Определённые интегралы. А программисты делают так
Программисты начинают с того места, где остановился академик Лузин,
потому что у него не было программистов. Программисты тупо считают
сумму площадей прямоугольников. Вот именно тех, что на картинке и в
пределе, который соответствует значению определённого интеграла.
Чисто для расширения кругозора ­ в вычислительной математике это так и
называется ­ метод прямоугольников ­ the Method of Rectangles. Над
интерфейсом я долго думал:
function MOfRectangles(

F
a,b
delta
eps

:
:
:
:

TFunction;
double;
double;
d o u b l e = 0) : d o u b l e ;

Комментирую. Delta ­ ширина прямоугольника. Обычно предполагают,
что количество прямоугольников стремится к бесконечности, но тогда
ширина одного прямоугольника будет зависеть от интервала (a,b), что
безразлично для математического анализа, но не совсем хорошо для
программирования. Eps ­ параметр по умолчанию, если он не ноль, то delta
автоматически уменьшается до тех пор, пока разность между двумя
последовательными приближениями площади не станет меньше eps. Все
проверки на корректность входных данных вы добавите самостоятельно. И
ещё, математики для нас уже доказали, что совершенно безразлично,
вычислять ли значение функции по левой границе прямоугольника, по
правой, или где угодно внутри него. Оцените,насколько тупо и в лоб
реализована мною реализация:
var
numOf
oldResult
i

: integer;
: double;
: integer;

begin
result:=0;
if

eps = 0 t h e n b e g i n
numOf:=Floor((b­a)/delta);
f o r i : = 1 t o numOf do
result:=result + delta*F(a+i*delta);

end
e l s e begin
repeat
oldResult:=result;
numOf:=Floor((b­a)/delta);

221

result:=0;
f o r i : = 1 t o numOf do
result:=result + delta*F(a+i*delta);
delta:=delta/10;
u n t i l A b s ( o l d R e s u l t ­ r e s u l t ) < Eps;
end;
end;

А вот результаты применения метода для очень хорошей функции
f (x) = л/2(x ­ 2) :
2

delta

сумма

0.1
0.01
0.001
0.0001

2.949
3.264
3.296
3.299

отклонение
%
10.64
1.10
0.12
0.03

Если задействовать параметр по умолчанию и задать eps=0.001, то
результат будет 3.300. Это не значит, что мы достигли абсолютной
точности, значение конкретно этого интеграла вообще иррационально ­
» ^ 3 , это значит, что погрешность лежит за третьим знаком после точки.
Если мы поинтересуемся, чему к этому моменту равно delta, то
обнаружим, что его значение равно 10 = 0.000001 . Здесь может
возникнуть некоторое недопонимание, ведь из таблицы видно, что при
delta = 10 отклонение от теоретического результата в пределах 0.001.
Но обратите внимание, что мы требуем несколько большего ­ мы требуем
чтобы разность двух последовательных приближений была меньше 0.001.
6

6

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

222

rdx r e r sin x
­ ^ Ш[ ^ ~x~ ^ x ' ^
x

t o

з д е с ь

Д° понять ­ неопределённого интеграла

на

нет, а определённый есть. Он не может не есть! То есть, его не может не
быть, ведь определённый интеграл ­ всего­навсего площадь над или под
кривой. Возьмём функцию
показательной функцией, видимо потому, что интеграл от неё не берётся.
График выглядит заурядно и заунывно.

Обратите внимание, что график начинается с единицы. Объясните себе и
окружающим причину. Запишите причину на бумажку и прилепите её
магнитиком к системному блоку или холодильнику.
Запускаем программу. Поскольку программа не знает, что интеграл не
берётся, то быстро получаем ответ: J
2

~

4 . 9 8 0

. А говорили не

x

берётся, не берётся...
Теперь любимая мною тема ­ стоит ли улучшать программу? Повторю ещё
раз картинку с методом прямоугольников:

223

А нельзя ли улучшить? Мы проводим вертикальную линию из точки x=7
до пересечения её с кривой. Затем проводим горизонтальную линию до
пересечения её с вертикальной, восстановленной из точки x=7.5. Иначе
говоря, мы провели линию из точки A до точки B . Немного подумав,
задаем вопрос ­ а не лучше ли провести линию сразу в точку С ? Результат
на следующей картине:

С первого взгляда видно, что фигура заполнена гораздо лучше, отчасти
потому, что функция у нас очень хорошая. Что случилось с
геометрической точки зрения? Прямоугольники мы заменили на
прямоугольные трапеции. Выглядят они не вполне канонически, потому
что поставлены боком. Алгоритм расчёта суммы нисколько не меняется.
Меняется только формула расчёта элементарной фигуры. Для
прямоугольника S = ah , где, в нашей терминологии,

a

=

f

( x

n

)

, а

h = delta. Для прямоугольной трапеции S = (a + b)h / 2, где a, h = то же
самое,

b

=

f

( x

„ ) . Программа слегка меняется:
+ 1

var
numOf

integer;

f i , f i p 1

double;

oldResult

double;

224

i

: integer;

begin
result:=0;
if

eps = 0 t h e n b e g i n
numOf:=Floor((b­a)/delta);
f o r i : = 1 t o numOf do b e g i n
fi:=F(a + i*delta);
fip1:=F(a + (i+1)*delta);
result:=result + delta*((fi+fip1)/2);
end;

end
e l s e begin
repeat
oldResult:=result;
numOf:=Floor((b­a)/delta);
result:=0;
f o r i : = 1 t o numOf do b e g i n
fi:=F(a + i*delta);
fip1:=F(a + (i+1)*delta);
result:=result + delta*((fi+fip1)/2);
end;
delta:=delta/10;
u n t i l A b s ( o l d R e s u l t ­ r e s u l t ) < Eps;
end;
end;

Если код кажется вам скучным и затянутым ­ оптимизируйте. Лично мне
всё нравится, за исключением вот этого delta:=delta/10; Нельзя ли задать
вместо десятки параметр, или, что лучше, подбирать делитель
динамически? Когда вы отработаете подбор делителя до совершенства,
сравните метод прямоугольников и метод трапеций. В качестве критерия
используйте количество вызовов функции. Однако, мы отвлеклись ­ вот
сравнительная таблица эффективности обоих методов при равных шагах:
сумма
м. прямоугольников
2.949
0.1
0.01
3.264
0.001
3.296
0.0001 3.299
delta

отклонение
%
10.64
1.10
0.12
0.03

сумма
м. трапеций
3.146
3.286
3.298

отклонение
%
4.7
0.42
0.06

Эффект особенно ощутим для приближений с грубым шагом. Оно и
понятно, при основании 0.0001, какая разница, что на этом основании
возвышается ­ прямоугольник или трапеция?

225

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

6

2

К сожалению, математическое искусство стремительно обесценивается.
Всё побеждает грубая вычислительная сила в лице тупого
метода
прямоугольников.
Определённые интегралы. Не только это
Разумеется, на этом интегралы не кончаются, не кончаются даже в смысле
способов их программно вычислять. Потому что внезапно выясняется, что
интегралы бывают очень разные.
Сначала двойные, тройные и вообще кратные интегралы. Двойные
интегралы в мозг простого человека помещаются легко, тройные с
некоторым усилием, далее труднее. Двойной интеграл ­ число. И найти это
число программно часто проще, чем аналитически. В чём смысл двойного
интеграла? Сначала вспомним, в чём смысл простого определённого
интеграла. Есть кривая, мы движемся по оси X от a до b и вычисляем
площадь над осью. Занудства ради, если кривая уходит вниз, под ось, то
мы из площади сверху вычитаем площадь снизу. А двойной интеграл, это
то же самое, но в пространстве.
Перечитайте Фихтенгольца, он занудный но понятный. Переслушайте
любимую песню моей юности Раскинулся вектор по модулю пять.

226

Приложение A
Простая процедура для рисования графиков
с уместными комментариями.
Длинное приложение, но полезное. Но длинное
Далее вашему вниманию предлагается действительно очень полезный
класс для рисования графиков. Простой, но незатейливый. Примитивный,
но несложный. Короче, вам обязательно понравится. Комментарии после
программного кода. Их, комментарии, никто не читает, но вы прочитайте,
пожалуйста. А здесь предварительные вводные замечания общего
характера.
Кстати, рекомендую, свеженькая книжечка:
Г.Е.Шилов «Как строить графики», М. Государственное
физико­математической литературы, 1959

издательство

Этот класс предназначен для отображения традиционных, даже, не
побоюсь этого слова, аналитических функций, не в каноническом смысле,
конечно. Имеется в виду, что на вход функции подаётся действительный
аргумент и результатом функции является действительное число. Иными
словами, для отображения сугубо целочисленных функций класс не
вполне походит. Он их нарисует, конечно, но не вполне идеально и
заказчику картинка не понравится. Это хорошо видно в главе о теории
вероятностей, на примере карточной колоды.
Но вы можете всё исправить, это же класс ­ просто перепишите
виртуальный метод. Ещё обратите внимание на свойства. Там, где метод,
реализующий запись (директива write), обязательно должны быть
проверки на корректность параметров. Но мы же пишем для себя, можно и
так?
Ведь мы играем не из денег,
А чтобы вечность проводить. © Ac Пушкин
А вообще, должен вам признаться, главное в написании любого класса ­
определить его интерфейс (interface). Реализация (implementation)
гораздо проще, и ошибки там гораздо проще исправлять.
227

Интерфейс
unit

SiGraphic;

{
interface
uses
Types,
{

//

18.09.2016

//

27.09.2016

Graphics;

const
maxNum = 1 6 ;
type
TSiMathFunc = f u n c t i o n (
type
TSiGraphic = class
private
fNumOf
fC
fBegX
fMsmX
fBegY
fMsmY
fRect
fColor
fWidth
fF

x : single)

:
:
:
:
:
:
:
:
:
:

: single;

integer;
TCanvas;
single;
single;
single;
single;
TRect;
array[1..maxNum] o f TColor;
array[1..maxNum] o f i n t e g e r ;
array[1..maxNum] o f TSiMathFunc;

f u n c t i o n GetF(
procedure SetF(

i n d : i n t e g e r ) : TSiMathFunc;
i n d : integer;
v a l u e : TSiMathFunc);
function GetColor(
i n d : i n t e g e r ) : TColor;
procedure SetColor(
i n d : integer;
value : TColor);
function GetWidth(
i n d : integer) : integer;
procedure SetWidth(
i n d : integer;
value : integer);
public
property
property
property
property
property
property
property
property
property

numOf : i n t e g e r r e a d fNumOf w r i t e fNumOf;
F [ i n d : i n t e g e r ] : TSiMathFunc r e a d GetF w r i t e
C
TCanvas r e a d f C w r i t e f C ;
begX
s i n g l e r e a d f B e g X w r i t e fBegX;
MsmX
s i n g l e r e a d fMsmX w r i t e fMsmX;
begY
s i n g l e r e a d fBegY w r i t e fBegY;
MsmY
s i n g l e r e a d fMsmY w r i t e fMsmY;
rect
TRect
read fRect write fRect;
color[ind:integer]
TColor read GetColor

SetColor;

228

SetF;

write

property

width[ind:integer]

: integer

read GetWidth

write

SetWidth;
constructor Create;
destructor Destroy;
p r o c e d u r e AddF
(
procedure AddFExt(

procedure

SetAll(

override;
wF
wF
wColor
wWidth

wBegX,wMsmX, wBegY,wmsmY
wRect

procedure
procedure
procedure

Field;
AxisOnly;
LegendVer(

procedure

LegendHor(

procedure
procedure

OneFunction(
AllFunctions;

private
MXpix,MYpix
x0,y0
end;

TSiMathFunc)
TSiMathFunc;
TColor;
integer);

marks
isOn
marks
isOn
num

array of single;
boolean = t r u e ) ;
array of single;
boolean = t r u e ) ;
integer);

single;
TRect);
virtual;
virtual;
virtual;
virtual;
virtual;
virtual;

: single;
: integer;

Комментирую. Константа maxNum задаёт максимальное количество
одновременно рисуемых на одном поле разных графиков разных функций.
В реализации никаких ощутимых ограничений не заложено, но мне
показалось, что шестнадцати кривых более, чем достаточно ­ а какое­то
ограничение должно быть, в нашей реализации. Функции не обязаны быть
разными, это может быть и одна и та же функция, не в смысле
математическом ­ это само собой разумеется, а в смысле программном. В
этой конкретной версии класса в этой возможности смысла нет ­ масштабы
и начала отсчёта для всех кривых совпадают. Нельзя отобразить одну и ту
же кривую в разных масштабах, а иногда хочется.
TSiMathFunc ­ это объявление нашей функции как процедурного типа.
Сразу после секции реализации будет простой пример, как этим
пользоваться. Все методы, для которых это имеет смысл, объявлены как
виртуальные. Простой пример на эту тему тоже чуть дальше.
Свойства. C: TCanvas ­ на чём рисовать. Color, width ­ цвет и ширина
линий, свойства индексированные. BegX, BegY ­ начала отсчётов в
физических единицах. MsmX, MsmY ­ масштабы в физических единицах
на сантиметр экрана (разумеется, очень приблизительно).
229

Винтовки Мосина, я думаю, вас не заинтересуют © Брат­2
В смысле, конструктор с деструктором вас вряд ли заинтересуют, поэтому
переходим к методам. AddF просто добавляет функцию для отображения.
AddFExt тоже добавляет, но не просто, а вместе с характеристиками
линии, которой она будет нарисована. Метод SetAll задаёт размеры поля,
начала отсчётов по осям и масштабы, как уже замечено, для всех функция
сразу. И, как легко подметить, все эти методы не производят никакого
видимого эффекта.
Field рисует поле вывода, просто поле. А что там вообще рисовать? Не
знаю, главное ­ метод виртуальный, то есть его можно переписать в
порождённом классе под свои извращённые потребности. AxisOnly, как
следует из названия, только рисует оси координат. LegendVer и LegendHor
наносят числовую разметку на оси, стандартно ­ линейную,
соответственно параметрам метода SetAll. Оставшиеся два метода,
OneFunction и AllFunctions, как нетрудно догадаться, рисуют графики.
Реализация
Теперь реализация, она относительно большая, а мне не нравятся в книгах
программные коды на несколько страниц. Поэтому реализацию разобьём
на части и будем перебивать комментариями.
implementation
uses
SysUtils;
const
pSm = 30;
{========================

TSiGraphic

=======================}

constructor TSiGraphic.Create;
begin
inherited;
fNumOf:=0;
C:=nil;
fBegX:=0;
fMsmX:=0;
fBegY:=0;
fMsmY:=0;
fRect.Left:=0;

f R e c t . R i g h t : = 0 ; fRect.Top:=0;

230

fRect.Bottom:=0;

FillChar(
FillChar(

fColor,
fWidth,

SizeOf(fColor),# 0 ) ;
SizeOf(fWidth),# 0 ) ;

end;
{

}

destructor TSiGraphic.Destroy;
begin
inherited;
end;

Всё, что здесь заслуживает доброго слова ­ константа pSm. Это
предполагаемое количество пикселов на сантиметр. Предполагаемым оно
является втом смысле, что наверняка мы никогда знать его не можем ­
обдумайте. Если очень хочется, можно её/его перенести в секцию interface
и даже оформить в виде свойства.
procedure TSiGraphic.AddF(
begin
i f numOf < maxNum t h e n
numOf:=numOf + 1 ;
fF[numOf]:=wF;
end;
end;
{

wF

: TSiMathFunc);

begin

procedure TSiGraphic.AddFExt(

wF
wColor
wWidth

TSiMathFunc;
TColor;
integer);

begin
AddF( w F ) ;
color[numOf]:=wColor;
width[numOf]:=wWidth;
end;
{
procedure

TSiGraphic.SetAll(

wBegX,wMsmX, wBegY,wMsmY
wRect

begin
begX
MsmX
begY
MsmY
rect

=wBegX;
=wMsmX;
=wBegY;
=wMsmY;
=wRect;

end;

Здесь, как ни странно, всё должно быть понятно и так.
procedure T S i G r a p h i c . F i e l d ;
begin
i f C = n i l then E x i t ;
MXpix:=MsmX / Psm;
MYpix:=MsmY / Psm;

231

single;
TRect);

C.Brush.Color:=clWhite;
C.FillRect(C.ClipRect);
C.Pen.Width:=1;
C.Pen.Style:=psDot;
C.Rectangle(rect);
x0:=rect.Left;
y0:=(rect.Top+((rect.bottom­rect.Top)

d i v 2 ) ) + Round(begY/MYpix);

end;

Да, применение Exit идеологически невыдержанно, однако оно уменьшает
вложенность на один уровень.
procedure TSiGraphic.AxisOnly;
begin
C.Pen.Style:=psSolid;
C.Pen.Width:=1;
x0:=rect.Left;
y 0 : = ( r e c t . T o p + ( ( r e c t . b o t t o m ­ r e c t . T o p ) d i v 2 ) ) + Round(begY/MYpix);
C.MoveTo(x0, y 0 ) ;
C.LineTo(x0+rect.Right, y 0 ) ;
C.MoveTo(rect.Left,
C.LineTo(rect.Left,

rect.Top);
rect.Bottom);

end;
{
procedure TSiGraphic.LegendVer(
const
dx = ­20;
dy = ­5;
var
whY
i
begin
i f not isOn then
C.TextOut(

x0­dx,

marks
isOn

: array of single;
: boolean = t r u e ) ;

: integer;
: integer;
Exit;
y0­dy,

FloatToStrF(begY,

ffGeneral,

3,1));

f o r i : = L o w ( m a r k s ) t o H i g h ( m a r k s ) do b e g i n
whY:=y0­Round((marks[i]­BegY)/MYpix);
C.TextOut(x0+dx,whY+dy,FloatToStrF(marks[i],ffGeneral,
C.MoveTo(x0­5,whY);
C.LineTo(x0+5,whY);
end;

3,1));

end;

Обратите внимание ­ отметки по оси задаются явным способом, то есть
если мы хотим надписи в точках 0, 3.14, 10, 99, то так их ручками и задаём.
232

Я долго думал, и решил, что так будет лучше. Константы dx и dy нужны
для того, чтобы числа не налезали на оси и риски на них. А локальные они
потому, что никому вне этой процедуры они не нужны.
procedure
const
dx =
dy =
var
whX

TSiGraphic.LegendHor(

marks
isOn

: array of single;
: boolean = t r u e ) ;

­5;
10;
: integer;
integer;

begin
if

not isOn then E x i t ;

i : = L o w ( m a r k s ) t o H i g h ( m a r k s ) do b e g i n
whX:=x0+Round((marks[i]­BegX)/MXpix);
C.TextOut(WhX+dx,y0+dy,FloatToStrF(marks[i],ffGeneral,
C.MoveTo(whX,y0­5);
C.LineTo(whX,y0+5);
end;
for

3,1));

end;

Константы слегка мутировали. Подобраны опытным путём. Если не
трудно, выведите их математически и обоснуйте.
Обоснуй!
От обоснуя слышу! © Шутка юмора
procedure

TSiGraphic.OneFunction(

oneF
x,y
xP,yP
xPlast,

num

: integer);

TSiMathFunc;
single;
integer;
integer;
integer;

yPlast

begin
C.Pen.Width
C.Pen.Style
C.Pen.Color
xPlast:=0;
for

=width[num]
=psSolid;
=color[num]
yPlast:=0;

i : = 1 t o ( r e c t . R i g h t ­ r e c t . L e f t ) do b e g i n
x:=begX + ( i ­ 1 ) * M X p i x ;
oneF:=F[num];
y : = o n e F ( x ) ­ begY;
xP:=Round((x­begX)/MXpix);
yP:=­Round(y/MYpix);
if

i = 1 then

begin

233

C.Pixels[x0+xP, y0+yP]:=color[num];
end e l s e
i f i >= 2 t h e n b e g i n
C.MoveTo(x0+xPlast, y 0 + y P l a s t ) ;
C.LineTo(x0+xP, y0+yP);
end;
xPlast:=xP;
yPlast:=yP;
end;
end;
{

}

procedure T S i G r a p h i c . A l l F u n c t i o n s ;
var
i
: integer;
begin
f o r i : = 1 t o numOf do b e g i n
OneFunction(i);
end;
end;

А здесь всё самое главное. Зачем нужна промежуточная переменная oneF?
А затем, что без неё транслятор не пропускает. Это объяснению не
подлежит, а остальное понять можно, при желании. А подробно я это
объяснял в моей предыдущей книге.
function TSiGraphic.GetF(
i n d : i n t e g e r ) : TSiMathFunc;
begin
i f ( i n d > = 1 ) and (ind = 1 ) and (ind = 1 ) and (ind = 1 ) and (ind = 1 ) and (ind = 1 ) and (ind= x0 + r e c t . R i g h t ) ;
end;

Если надолго задуматься, то всё будет сразу понятно. А теперь результат:

о

•1

Нули в начале координат ­ атавизм. Самостоятельно найдите, что надо
поправить и какой метод переписать, чтобы от них избавиться.
Что с этим можно и нужно делать дальше? Первое, на что я уже намекал.
Для каждой функции должна быть возможность задать свои начала
отсчёта и свои масштабы. Даже если это одна и та же функция. Второе,
менее очевидное. Наш класс плохо подходит для рисования функций от
целочисленного аргумента. То есть, он их, безусловно отобразит, но как­то
неубедительно, неэротично. Подумайте о возможных способах улучшения
ситуации.

237

Приложение В
Просто колода карт.
Полезна для простых опытов из теории
вероятностей
Если мы вытащим случайную карту из колоды, какой она будет? Легко ли
написать программу, эмулирующую (красивое слово!) эту операцию.
Причём аккуратно, соблюдая все постулаты Теории Вероятности? Легко,
причём очень легко. Наша программа может каждый раз возвращать
Бубновый Туз (БТ), при каждом её, программы, запуске. Почему бы и нет?
Если мы извлекаем из колоды одну, случайную, карту, почему бы ей не
оказаться БТ? При каждом отдельно взятом запуске программы, само
собой. Реализация функциональности обещает быть несложной и, что
важнее, очень надёжной:
function ReallySimpleAsolutelyRandorrManyCardsGeneretor
(
skoka : i n t e g e r ) : TCard;
var
i
: integer;
begin
f o r i : = 1 t o s k o k a do b e g i n
result.suit:=diamonds;
result.rank:=Ace;
ShowMessage(
rankNames[result.rank]
suitNames[result.suit]);
end;

+

'

of

'

+

end;

А это заранее подготовленный тест:
ReallySimpleAsolutelyRandomManyCardsGeneretor( 5 ) ;

Разумеется, у внимательного читателя возникли два вопроса ­ один
очевидный, другой ­ нетривиальный.
Вопрос номер один, лёгкий ­ а где, собственно объявления
многочисленных используемых типов и констант ­ TCard, diamonds, Ace,
rankNames, suitNames? Не надо тревожиться, ситуация под контролем, всё
будет чуть позже, в полнофункциональной версии модуля, всего за 229.95
руб., а для покупателей этой книги, само собой, бесплатно.

238

Вопрос номер две, сложный ­ а можно ли рассматривать результаты нашей
процедуры как действительно случайные карты? Буду бить козырем, то
есть сошлюсь на непререкаемый авторитет:
Любая конкретная последовательность, содержащая миллион цифр, так
же вероятна, как и любая другая. Если мы выберем миллион цифр наудачу
и если окажется, что первые 999999 из них ­ нули, то вероятность того,
что последняя цифра в этой последовательности ­ также нуль, всё ещё
останется точно равной одной десятой, в истинно случайной ситуации.
Это утверждение большинству кажется парадоксальным, однако оно не
противоречит реальности.
© Кнут, том второй
Если от нашей программы требуется изобразить результат извлечения
двух случайных карт, то два БТ будут выглядеть странно, даже при
условии возвращения извлеченной карты в колоду. Говоря по другому,
наша программа приобретает нетривиальный вид.
А теперь поставим вопрос ­ чего мы от нашей программы ждём? Какие
манипуляции с колодой она должна выполнять, не нарушая законов
вселенной и формулировки Е.С. Вентцель , чисто немецкая фамилия, а вы
что подумали, если что? ­ хотя я уже разъяснял.
От нашей колоды требуется немного ­ если мы попросим её, колоду,
извлечь несколько случайных карт, то они, карты, должны быть
случайными, или, по крайней мере, быть псевдослучайными ­ то есть
выглядеть как случайные. Это уже неплохо. Дальше есть варианты. Если
мы попросим выдать две карты, то при этом первая карта может быть
возвращена в колоду. А может и не быть возвращена. По простому ­ если
мы получили ТП в первый раз, то, при возврате карты в колоду, он может к
нам вернуться и во второй раз. Если карту в колоду вернуть уже нельзя то
ТП нас не ожидает, максимум КП ­ король пик.
Ещё очень неплохо иметь возможность выводить результаты не в виде
удобных для программиста, но никому не понятных внутренних
кодировок, а как­то проще, по­человечески, что ли. То есть не «1­9» и даже
не «ТП», а «Туз Пик». Значит, надо реализовать несложную, но в целом
занудную, процедуру конвертации внутренних типов в человеческие.

239

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

SiDeck;

//

18.09.2016

//

09.07.2017

{
interface
uses
Classes;
{
const
numOfSuits
numOfRanks
numOfCards

}

}
= 4;
= 13;
= n u m O f S u i t s * numOfRanks;

type
TSuit = ( n o S u i t , spades, c l u b s , diamonds, h e a r t s ) ;
TRank =
(noRank,two,three,four,five,six,seven,eight,nine,ten,
Jack,Queen,King,Ace);
TCard = p a c k e d r e c o r d
suit
: TSuit;
rank
: TRank;
inDeck
: boolean;
end;
const
noneCard

: TCard

=

(suit:noSuit;

rank:noRank;

inDeck:false);

type
TDeckArray = array[1..numOfCards] of i n t e g e r ;
const
suitNames

: array[noSuit..hearts] of s t r i n g =
( 'no',
'spades
',
'clubs
',
'diamonds',
'hearts ' ) ;
rankNames
: array[noRank..Ace] of s t r i n g =
('no',
'two','three','four','five','six','seven','eight','nine','ten',
'Jack','Queen','King','Ace');
suitNamesShort
: array[noSuit..hearts] of s t r i n g =
( 'no',
'S',
'C',

240

'D',
'H');
rankNamesShort
: array[noRank..Ace] of s t r i n g
('no',
' 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 1 0 ' , ' J ' , 'Q', 'K', ' A ' ) ;
,

,

I

,

,

,

,

,

,

,

,

I

,

,

,

=

,

type
TDeck = c l a s s
private
f u n c t i o n GetNumNominal : i n t e g e r ;
f u n c t i o n GetNumOf : i n t e g e r ;
public
constructor Create;
destructor Destroy;
p r o p e r t y numNominal
p r o p e r t y numOf

override;
: integer
: integer

read
read

GetNumNominal;
GetNumOf;

procedure RandomCardWithReturn( v a r card
procedure RandomCardWithoutReturn( v a r card
private
c
end;
function
function
function
function

: TCard);
: TCard);

: a r r a y [ s p a d e s . . h e a r t s , two..Ace] o f TCard;

SameCard(
c a r d 1 , c a r d 2 : TCard) : b o o l e a n ;
CardNumToCard(
num : i n t e g e r ) : T C a r d ;
CardToCardName(
c a r d : TCard) : s t r i n g ;
CardNumToCardName(
num : i n t e g e r ) : s t r i n g ;

Комментарии. Колода предполагается большая, 52 карты. Если нам
понадобится 36, можно написать порождённый класс. Хотя лично мне
кажется более удобным вариант добавить параметр в конструктор ­ тип
колоды. В типах и константах разобраться несложно, если помнить как это
всё называется по­английски. По­английски оно всё не потому, что я
выпендриваюсь, а потому, что моё обострённое эстетическое чувство не
допускает писать латиницей что­то вроде semerka, Tuz, а только seven, Ace.
// Ностальгия
Когда я программировал на языке Pascal для отечественного клона
машины DEC PDP­1, там можно было писать идентификаторы по­русски ­
семёрка, Туз. Только потом препроцессор всё равно переводил это в
латиницу ­ semerka, Tuz...
// конец Ностальгии
Функции в хвосте, не относящиеся к классу, обеспечивают ненавязчивый
241

сервис ­ позволяют обращаться к карте по номеру, предполагая некоторые
соглашения по нумерации. Для чего это надо? Так бывает проще, внутри
класса. Увидите, изучая код реализации. Кроме того, нельзя так просто
взять, и сравнить на равенство две карты ­ пришлось написать функцию.
О самом классе ­ в нём нет почти ничего, но кое­что всё­таки есть.
numNominal всегда возвращает то количество карт, которое в колоде было
изначально ­ 52 в нашем случае. numOf ­ количество карт, которое в
колоде осталось, если сколько­то из них извлекли и в колоду не вернули.
Соответственно, имеем методы:
procedure RandomCardWithReturn( v a r c a r d
procedure RandomCardWithoutReturn( v a r card

: TCard);
: TCard);

Первый метод извлекает карту из колоды с возвратом, второй с возвратом.
То есть, для первого метода, если извлечён Туз Пик, то и при следующей
попытке есть шанс, что это событие повторится. Для второго метода это
исключено.
Теперь реализация класса:
constructor
TDeck.Create;
var
card
i
k
begin
inherited;

TCard;
TSuit;
TRank;

Randomize;
i : = s p a d e s t o h e a r t s do b e g i n
f o r k : = t w o t o Ace do b e g i n
card.suit:=TSuit(i);
card.rank:=TRank(k);
card.inDeck:=true;
c[i,k]:=card;
end;
end;
for

end;
{
destructor TDeck.Destroy;
begin
inherited;
end;
{

242

procedure TDeck.RandomCardWithReturn(
v a r card
var
nCard
: integer;
begin
nCard:=Random(numNominal) + 1;
card:=CardNumToCard(nCard);
end;
{
procedure

TDeck.RandomCardWithoutReturn(

nCard
nomer

v a r card

: TCard);

TCard)

integer;
integer;
TSuit;
TRank;

k
begin
card:=noneCard;
nCard:=Random(numOf) + 1 ;
nomer:=0;

i : = s p a d e s t o h e a r t s do b e g i n
f o r k : = t w o t o Ace do b e g i n
i f c [ i , k ] . i n D e c k then begin
nomer:=nomer + 1 ;
i f nomer = n C a r d t h e n b e g i n
card:=c[i,k];
c[i,k].inDeck:=false;
end;
end;
end;
end;
for

end;
{

Set&Get

procedures

for

f u n c t i o n TDeck.GetNumNominal : i n t e g e r ;
begin
result:=numOfSuits*numOfRanks;
end;
{
function
var

TDeck.GetNumOf

integer;
TSuit;
TRank;

begin
result:=0;
i : = s p a d e s t o h e a r t s do b e g i n
f o r k : = t w o t o Ace do b e g i n
if c[i,k].inDeck
then r e s u l t : = r e s u l t + 1;
end;
end;
for

end;

243

prroperties

}

Некоторых умственных усилий потребовала только реализация
извлечения карты без возврата. Ещё больших усилий понадобилось на
тестирование этого продукта. Как убедиться, что карты не повторяются?
Сравнивать во вложенном цикле? Сначала отсортировать? Можно, но
сначала надо написать функцию сравнения двух карт ­ какая больше,
какая меньше. Я пошел простейшим, хотя и халтурным путём ­ затребовал
из колоды поочерёдно 53 карты, вот так:
D:=TDeck.Create;
stroka:='';
f o r i : = 1 t o 53 do b e g i n
D.RandomCardWithoutReturn(card);
s t r o k a : = s t r o k a + CardToCardName(card) + #13#10;
end;
ShowMessage(stroka);
D.Free;

Последняя,
пятьдесят
третья
карта,
оказалась
специально
предусмотренной для этого случая инициализированной константой
noneCard. Перевожу ­ это означает, что других карт в колоде не нашлось. С
этого момента я счёл класс годным для применения. А теперь, для полного
счастья, обещанные сервисные функции:
f u n c t i o n SameCard(
c a r d 1 , c a r d 2 : TCard) : b o o l e a n ;
begin
r e s u l t : = ( c a r d 1 . s u i t = c a r d 2 . s u i t ) and ( c a r d 1 . r a n k = c a r d 2 . r a n k ) ;
end;
{
}
f u n c t i o n CardNumToCard(
num : i n t e g e r ) : T C a r d ;
var
suit
: TSuit;
rank
: TRank;
begin
s u i t : = T S u i t ( ( ( n u m ­ 1) d i v numOfRanks) + 1 ) ;
r a n k : = T R a n k ( ( ( n u m ­ 1) mod numOfRanks) + 1 ) ;
result.suit:=suit;
result.rank:=rank;
end;
{

}

f u n c t i o n CardToCardName(
c a r d : TCard) : s t r i n g ;
begin
result:=rankNames[card.rank] + ' ' + suitNames[card.suit];
end;
{
}

244

f u n c t i o n CardNumToCardName(
begin
result:=CardToCardName(
end;

num

: integer)

: string;

CardNumToCard( n u m ) ) ;

245

Приложение C
Та Самая Гравюра из Невского Альманаха
И бонус

246

И обещанный бонус. Картинок было две, по крайней мере Александр
Сергеич откликнулся на две:
Вот, перешед чрез мост Кокушкин,
Опёршись жопой о гранит,
Сам Александр Сергеич Пушкин
С мосье Онегиным стоит.
Не удостаивая взглядом
Твердыню власти роковой,
Он к крепости стал гордо задом ­
Не плюй в колодец, милый мой.

247

Приложение D
Баллада о синусе
Мораль басни обычно приводится в конце, сейчас будет наоборот.
Во­первых, это не басня, а быль ­ жанр другой. Во­вторых, мораль такова,
что уместна в любом контексте, хоть спереди, хоть сзади. Мораль в том,
что если вы в чём­то уверены, то должны отстаивать свою точку зрения до
конца, особенно если это для вас выгодно. Если мы обнаруживаем, что
наша точка зрения заводит нас в тупик и в более плохие места, наша задача
от точки зрения отказаться и переобуться в воздухе. Основной принцип
программиста ­ программист всегда прав.
Было это давно. Из участников, активных и пассивных, никого уже нет. Не
в смысле померли, хотя, возможно и померли. Нет, просто сменили сферу
деятельности или страну, или ориентацию, или всё.
Мы разрабатывали сложный агрегат. Кто­то паял железо, а мы
программировали программы. Всё было впервые. Дисплей был
плазменный, впервые. Кстати, вертикального расположения, то есть по оси
Y точек было больше, чем по оси X, что несколько необычно и сейчас.
Генератор синуса генерировал синус, провода от генератора шли на АЦП
(алфавитно­цифровой преобразователь). Провода от АЦП шли на
управляющий всем компьютер. Компьютер был советским клоном PDP­1,
в отличие от монитора, АЦП, плоттера и прочего ­ всё это было абсолютно
отечественной разработки. Особенно отечественность чувствовалась в
электростатическом плоттере ­ для работы в него требовалось залить
авиационный керосин. В комплекте с плазменным монитором, наоборот,
прибыли два бандеровца для наладки. Это я сейчас осознаю, что они были
натуральные бандеровцы, тогда нам что­то парили про дружбу народов на
всех трёх телевизионных каналах, хотя смутные сомнения уже терзали.
Разговаривали эти смуглые хлопы меж себя на какой­то гваре, которой не
понимали даже местные, вполне натурализовавшиеся украинцы. Что такое
гвара, посмотрите статью в Википедии, и задумайтесь, троллинг это или
как?
Но, отдадим должное, дисплей под их руководством работал исправно.
Вот что Советская Власть животворящая делала!

248

Дисплей работал ­ как один из элементов системы. Система в целом не
работала. При первом же запуске по экрану (оранжевому) вместо легко
узнаваемого синуса пополз снизу вверх трудно узнаваемый синус. То есть
понятно, что это был график какой­то периодической функции, отдалённо
напоминавшеё синус. Но, к сожалению, именно, что был и именно, что
напоминавшей. Кривая с примерно равными интервалами по высоте то
достигала максимума, то уходила в минимум. При этом кривую не то,
чтобы плющило и колбасило, а, скорее что­то подвергало лечению
электрошоком.
Я тогда был ещё молодой, хотя и опытный. Я отвечал не за всё, а только за
визуализацию данных, поэтому именно меня и призвали к ответу ­ что за
фигня? Хотя я был и неопытным программистом, но кое­что я понимал. Я
понимал, что надо переводить стрелки. Если нельзя перевести на
конкретное лицо, то хотя бы на законы природы.
Я убедительно изложил причины, по которым синус в принципе не мог
отобразиться не то, чтобы идеально, но хотя бы правильно. Причинами
были
объявлены
потеря
точности
при
аналого­цифровых
преобразованиях, ограниченная длина машинного слова, особенности
округления в используемом языке программирования, недостаточное
разрешение дисплея (800x600 между прочим), а также недостатки
применяемого алгоритма рисования прямых линий ­ у монитора по сути
была только одна прошитая команда ­ нарисовать/стереть точку в
заданной позиции. Ещё можно было стереть всё. Вот это всё, выделенное
курсивом, и губило просто всё и без курсива. Повторюсь, я излагал очень
убедительно.
В разгар моей речи кто­то заслушавшийся сделал неловкое движение и
выдрал провод, шедший от генератора синуса на АЦП. Ничего не
произошло ­ по экрану ползла та же злобная пародия на синус,
страдающий Паркинсоном ­ хотя связи уже не было. Элементарное
расследование обнаружило, что связи не было и раньше ­ точно так же и
ещё раньше был оторван провод, идущий от АЦА к дисплею. Как только
оба провода вернули на их законные места, по монитору плавно пополз
идеально гладкий синус оранжевого цвета.
Говоря по­старому, в чём мораль? Или, формулируя по­другому, какие
выводы я сделал?
249

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

250

Приложение E , печальное.
Чего в книге нет, но могло быть
В самом начале я написал план книги. План был незатейлив ­ это были
двенадцать строчек. Каждая строка ­ глава. Каждая глава ­ раздел
математики. Через некоторое время обнаружилось, что одни главы
пишутся быстро, другие медленно, а совсем другие вообще не пишутся.
Так вот, далее список того, что здесь могло бы быть, но его нет. Но сначала
маленькое вступление и провокационный вопрос ­ как вы относитесь к
Дарвину и его теории естественного отбора?
Вопрос совсем не пустой и не тривиальный. Одни считают, что это полный
бред и всеми раскопками опровергнуто ­ всё было создано сразу и вдруг ­
кем именно, не важно. Другие, которые как бы за Дарвина, тоже
поделились. Есть сторонники Дарвина в натуре, то есть теории
индивидуального отбора. А есть сторонник великого советского генетика
академика Лысенко, то есть теории группового отбора. Поскольку ни то,
ни другой, не американцы, шансов у них не было. И тогда на поверхность
выполз совершенно американский профессор Докинз и предъявил отбор
на уровне гена, в своей книге Эгоистичный ген, рекомендую к прочтению.
К сожалению, не все слова в этой книге лично для меня оказались
понятными. Даже и не все предложения и не все абзацы. Профессор
Докинз это телепатически почуял и пошёл читателю­математику
навстречу.
Следующая его книга называлась Слепой часовщик. Там слова были
понятны все. Профессор взял учебник по теории вероятностей, учебник по
теории игр и учебник по теории графов. Затем он их прочёл ­ не целиком,
конечно, но в пределах первой главы. У профессора наступило
просветление. После чего он прочёл учебник для начинающих по языку
программирования Basic. Прочёл, разумеется, целиком ­ в этом случае от
первой главы толку ноль. И сразу стал всё это применять к естественному
отбору. Через некоторое время он даже понял, что программа должна
куда­то сохранять результаты своей работы, а исходные тексты программ
неплохо резервировать.
Я никаким образом не иронизирую. Генетик прочел несколько
популярных книжек по математике и внезапно обнаружил сколько изо
всей этой математики имеет явное и полезное применение в генетике. А
почему программисты этих книжек не читают? Вопрос риторический, в
смысле ­ ответ и дураку понятен.
251

Так вот, теперь о нескольких темах, о чём программисту прочитать
неплохо бы. Их того, что перечислил профессор, с теорией вероятностей
вы уже знакомы. Об остальном и поговорим.
Матрицы и Высшая Алгебра
Высшая алгебра вообще не о том и программисту не нужна. Не нужна
совершенно. Но часто под видом высшей алгебры пытаются продать
банальные матрицы. В этом есть свой резон ­ матрица один из объектов
высшей алгебры. Матрицы подозрительно похожи на массивы.
Применимы ли операции с матрицами к массивам и когда?
Системы линейных уравнений ­ что это такое и как их решать. Это
отдельная отрасль древа математической науки. Называется это ­ линейное
программирование.
Программирование
это,
напоминаю,
к
программированию в человеческом смысле отношения не имеет. Для чего
нужно? Для всего, в основном для планирования. Раньше составляли
планы для предприятий. На вход сырьё, есть станки с известной
производительностью, есть трудящиеся для станков. Как из всего этого
произвести продукцию наибольшей стоимости? Такого рода программы
крутились на каждом советском предприятии, толку было очень мало.
Тем не менее, знать это надо. Буквально под рукой валяется простая
книжка. Но старая. Но простая. Потому и простая, что старая:
А.С
Солодовников,
Введение
в линейную
программирование, М, Просвещение, 1966

алгебру

и

линейное

Предназначено для педагогических институтов. Тогда это звучало почти
как оскорбление. Ещё раз ­ это надо знать. Но об этом я уже пишу книгу.
Ещё напрашивается мысль ­ написать класс для матрицы. Сначала для
матрицы квадратной, а затем класс порождённый ­
матрица
прямоугольная. Или для частного случая ­ матрица­столбец­строка ­
предпочтительнее свой класс?
Графы и около
Тот же вопрос ­ а нужно ли это программисту вообще? В каждой второй
книге чисто по программированию проектируют деревья в виде списков.
Если язык программирования поддерживает ООП ­
Объектно
Ориентированное Программирование ­ то пишут соответствующий класс.
Иногда надо. Даже часто надо, но только в форме банальных деревьев. Чем
дерево отличается от графа, разберитесь сами.
252

Теория игр
Я уже рассказывал об играх в главах о теории вероятностей. Так вот, это
всё не о том. Большинство тех игр совершенно не интересуют теорию игр.
// Воспоминания о тревожной молодости
как­то нашу группу занесло на специализацию по кафедре теории игр.
Через полгода нас перебросили на кафедру вычислительной математики.
Бывает. Это я всё к тому, что заведующий кафедрой теории игр был самый
тоскливый, унылый и занудный мужик, который мне встречался. Это как в
мемуарах Юрия Никулина ­ были у него два знакомых клоуна, по
фамилиям ­ Преступляк и Кровопущенко.
// конец Воспоминаний
Для теории игр нужно как минимум два игрока, каждый из которых
принимает решение. Понятно, что рулетка или кости сюда не относятся ­
второй игрок механическая железяка или унылый геометрический
предмет. Очко тоже не сюда ­ второй игрок исключительно пассивен.
Вычислительная математика.
Всё то же, вид сбоку. Почему всё то же, и почему сбоку?
Вообще­то, эта книга могла быть и книгой только о вычислительной
математике, но мне показалось, что это будет очень скучно. Когда мы
расстались с кафедрой теории игр, во главе с неописуемо скучным
мужиком, нас перебросили на кафедру вычислительной математики. Люди
там были хорошие, веселые, но вот сам предмет невообразимо скучен ­ на
мой взгляд, по крайней мере.
Тот же вопрос ­ а нужно ли это программисту вообще? Нужно! Просто
потому, что все остальные разделы прикладной математики сводятся к
тому, чтобы что­то такое в конце концов посчитать. И вот здесь и
выползает на сцену вычислительная математика. Важное замечание ­
составной и неотъемлемой часть вычислительно математики является ­
или, по крайней мере, являлось в годы моей учёбы ­ математическое
программирование, то есть методы оптимизации. Этот раздел мне очень
симпатичен и приятен, и об этом будет моя следующая книга.
Что ещё надо знать из вычислительной математики? Что такое интерполяция
и что такое экстраполяция. И чем они отличаются между собой. Это важно.
Это нужно. Это применяется часто. Другое дело, что знать это надо, а вот
различать ­ совершенно не обязательно. Вот такой вот парадокс.
253

Аналитическая геометрия и немного человеческой
Сначала геометрия. Просто геометрия. Какая бывает геометрия вообще и в
частности?
Бывает геометрия школьная. Сначала что­то о треугольниках, потом
трапеции и круги. Даже не могу вспомнить, присутствовали ли в нашей
школьной геометрии эллипсы. Потом, в самых старших классах,
начиналась стереометрия ­ то, что не на плоскости, а в пространстве ­ куб,
пирамида, призма. Ещё была тригонометрия. Но её никто за геометрию не
считал, потому что там были только и сплошь формулы. Назовем это всё,
до тригонометрии, обычной геометрией.
А нужна ли вообще программисту обычная геометрия? В поисках ответа я
пошарил в ящиках стола и нашёл голубенький пластиковый стаканчик.
Применив недорогую пластмассовую Линзу Френеля ­ а нужна ли
программисту физика и знание о Линзе Френеля ­ я прочитал на её днище
ёмкость ­ 200 мл. Наши люди, как всем известно, миллилитрами не пьют,
наши люди пьют граммами, что смущает зарубежных барменов. Тем нем
менее, объёмные величины русскому человеку в целом понятны и
доступны, и нормальной единицей дискретизации кажутся сто грамм, они
же миллилитры, привет Менделееву.
Разумеется, сто миллилитров равны в точности ста граммам, только если
речь идёт о воде. После этой оговорки ставим несложную геометрическую
задачу. Нет, не пересчёт на плотность C H OH
Вспоминая об
арифметике, когда я хотел проверить навыки своих учеников, то просил
вычислить шестьдесят процентов от двадцати. Большинство было в шоке.
Справедливости ради, не все из них уже успели поступить в университеты.
2

5

Наша задача гораздо хитрее. Объём стаканчика, как я уже сказал, 200
кубических сантиметров. Визуально посудина представляет собой
усечённый конус. Верхний диаметр ­ 62 мм, нижний ­ 42. Высота ­ 85
миллиметров. До какой высоты следует набулькать туда жидкости, чтобы
получились сакральные сто грамм, они же миллилитры.
Если вы рассчитали результат в уме ­ решив пропорцию, составив
несложную систему уравнений, взяв определённый интеграл ­ вам будут
рады в любой компании ­ во всех смыслах слова компания.
254

Ванечка на секунду благоговейно замер... Он всегда разливал водку, среди
пьющих мужиков славился тем, что умел разливать на глаз любое
количество спиртного с такой точностью, что промеры спичкой
показывали абсолютную равностъ, и пьющие уважительно шептали:
«Глаз­алмаз». Был случай, когда Ванечка разлил три бутылки
«Столичной» в одиннадцать стаканов так, что в последней бутылке не
осталось ни капельки, а стаканы содержали ровно по сто тридцать
шесть граммов. © Виль Липатов «Серая мышь»
Я специально проверил, Липатов окончил исторический факультет, но
здесь мыслит как математик ­ хотя бы как и средневековый.
Теперь забудьте всё это ­ глаз­алмаз ­ и решите задачу как программист,
написав
процедуру
для
тупого
подбора
ответа.
Расширьте
функциональность на любой сосуд, заданный аналитической кривой.
Да, работать со сложными стереометрическими фигурами, программисту
приходится не каждый день. Хотя, вспомним Кеплера. Когда ему очень
захотелось кушать, он написал реферат под названием Стереометрия
винных бочек. Для программиста стереометрия слишком сложна, он живёт
или в одной плоскости или сразу в N­мерном пространстве. Там, в одной
плоскости, то есть в двумерном пространстве, мы и останемся.
Как именно отобразить геометрическую фигуру относится скорее к
ведению аналитической геометрии. К геометрии школьной, если можно
так её назвать, относится важный вопрос, достаточно ли у нас
информации, чтобы однозначно нарисовать фигуру. Обратите внимание, в
евклидовой геометрии для треугольника достаточно задать три его
вершины. В программистской или аналитической геометрии, для вершин
треугольника требуется задать их координаты. Нет координат ­ нет
треугольника.
Теперь подумайте об этом с точки зрения программиста ­ как задать
геометрическую фигуру­ треугольник, четырёхугольник, круг, эллипс.
Четырехугольник может быть конкретно параллелограммом, квадратом,
ромбом и прочей неведомой зверушкой. А раз вы таки программист,
подумайте о проверке ­ если вам велели нарисовать ромб, являются ли
задающие его данные действительно ромбом. И, разумеется, слово
нарисовать имеет здесь очень широкий неоднозначный смысл.
255

Ещё раз, программист имеет дело только с аналитической геометрией, не
потому, что он может выбирать, а потому, что всё давно уже выбрано за
него. Изобретатели всех возможных устройств отображения неизменно
имели перед собой как идеал Декартову систему координат.
Когда я немного преподавал программирование старшим школьникам и
младшим студентам, одной из задач, на вид простой, но вызывающей
множество затруднений при тщательном рассмотрении, было решение
обычного квадратного уравнения. А у нас для тренировки ­ простая свиду
задача. Нарисуем четырёхугольник. Нет, если это слишком трудно,
нарисуем хотя бы треугольник.
Только злобное требование ­ не рисуйте треугольник в Дельфи. Так всё
немного упрощается ­ есть метод пера MoveTo, который ничего не рисует,
но перемещает перо в требуемую точку, есть метод LineTo, который
рисует линию и перемещает перо в конец линии. Здесь всё просто,
запутаться трудно.
// сейчас Заплачу
Когда я был маленький ­ нет, не как вы сейчас, а реально маленький ­ у
мня были реальные бумажные книжки. И там были здоровенные тома под
названием Хочу всё знать! И в одном из них была статья о
роботах­черепашках, которые по командам свыше или по информации от
своих датчиков­фотоэлементов перемещались по плоскости. Банально по
нашему времени. Кроме того, у черепашки в заду було перо, которое она
по команде могла опускать или подымать и, соответственно, рисовать или
нет линию ­ прямую или не очень.
Никогда и не думал, что через много­много лет эта черепашка приползёт
ко мне ­ хотя и слегка виртуальная.
// перестал Плакать
Аналитическая геометрия ­ два слова о ней позже ­ указывает нам, как
именно рисовать. Геометрия простая объясняет, стоит ли вообще тратить
силы на попытку рисования. Достаточно ли нам данных, не противоречат
ли они между собой. Фигура, безусловно законная с точки зрения
геометрии, может на конкретном физическом устройстве отображения
выродиться в точку. Да, рассчитать многоугольник и нарисовать точку

256

стоит недорого, но таких неполноценных треугольников могут быть
тысячи и десятки тысяч.
И ещё, обычная и заурядна задача ­ есть две фигуры, пусть это окружность
и треугольник, простой случай. Найдите точки, в которых они
пересекаются.
Теперь аналитическая геометрия.
Просто для программистов
Начнем с того, с чего кончили в предыдущем разделе. Есть окружность и
треугольник, найдите точки их пересечения. Это бывает надо, не то, чтобы
каждый день надо, но бывает надо. Такие задачи проще решаются
методами аналитической геометрии. А методы аналитической геометрии
проще реализуются чисто компьютерными способами. То есть ­
компьютер есть ­ OK, нет компьютера ­ сиди кукуй.
А что такое вообще аналитическая геометрия? Самое простое ­ это
графики. О графиках я уже написал. Вообще говоря, аналитическая
геометрия ­ это когда геометрическая фигура задаётся не умелыми
жестами рук, а формулой. В подробностях я напишу об этом в своей
следующей после следующей книге. Просто следующая моя книга будет о
методах оптимизации.
В ожидании книги познакомьтесь системами координат ­ декартова,
полярная, ещё какие­то наверное есть? Расскажите мне. И, разумеется, я не
могу бросить вас вот так, на ровном месте, не рассказав чего­то супер
полезного. Самое, кроме шуток, полезное для программиста из
аналитической геометрии ­ это расстояние между точками. Пусть у нас
есть n­мерное пространство. В нём заданы две точки. Проблема в том, что
нет очевидного и простого способа эти точки задать. Очевидного ­ это
чтобы даже мне сразу было всё понятно. Ладно, пусть первая точка будет
(а 2 n)
,a

...a

а

, а вторая (b b b )
,

a

которое,

заметьте,

s = V ( a ­ b j ) + (а ­ b f
2

2

2

...

2

n

. Тогда расстояние между точками,
величина

+ ...(an ­ bn )

2

257

.

уже

безразмерная

Приложение F, радостное.
Чем заняться на досуге, по главам
Просто вообще
Великий русский поэт Александр Сергеевич Пушкин поступил в
Царскосельский Лицей. Принимали туда мальчиков дворянского сословия
в возрасте 10­12 лет. Обучения продолжалось шесть лет, окончание Лицея
приравнивалось к окончанию университета. А.С. учился слабовато и
закончил по второму разряду. Особенно слабовато А.С. учился по
математике. Ознакомьтесь с программой Лицея:
Начальный курс:
Математика:
арифметика, начиная с тройного правила;
простая геометрия;
алгебра до кубических уравнений;
тригонометрия прямолинейная.
Прикладная математика:
основания механики;
математическая география.
Окончательный курс:
Математика:
сферическая тригонометрия;
конические сечения;
Прикладная математика:
статика;
гидравлика;
артиллерия;
фортификация.
Оцените свои знания. Поставьте себе оценку, особливо по артиллерии и
фортификации. Обдумайте пункт из программы по словесности:
Продолжение переводов с изъяснением идиотизмов. Как у нас с этим?

258

Математическая логика.
Задания из Учебника логики для средней школы 1953­го года издания
1.

Страусы не летают, страусы — птицы. Какой следует вывод?
Какая фигура?

Фигура четвёртая, печальная © к/ф Любовь и голуби. Нет, фигура в
другом смысле
2.

Составьте суждения, равнозначные по содержанию следующим
суждениям:

Защита отечества есть священный долг каждого гражданина СССР.
3. Рассмотрите следующие деления, и если в них есть ошибки, то
укажите их [выбраны математические примеры]:
б) Числа делятся на целые, дробные, смешанные, именованные
отвлечённые.
в) Углы — прямые, тупые, острые, смежные, вертикальные.
3.

В каком отношении находятся между собой следующие понятия:

строение, клуб, дом, изба, дворец, Дворец Советов,
Зимний дворец, беседка, хата?

259

и

Рик Гаско

Простая Математика
для Простых Программистов
Ответственный за выпуск: В. Митин
Под редакцией: Н. Комлева
Обложка: СОЛОН­Пресс

По вопросам приобретения обращаться:
ООО «СОЛОН­Пресс»
123001, г. Москва, а/я 82
Телефоны: (495) 617­39­64, (495) 617­39­65
E­mail: kniga@solon­press.ru, www.solon­press.ru

ООО «СОЛОН­Пресс»
115487, г. Москва,
пр­кт Андропова, дом 38, помещение № 8, комната № 2.
Формат 60x88/16. Объем 16,25 п. л. Тираж 100 экз.