Рассказы о математике с примерами на языках Python и C

Елисеев Дмитрий

Вниманию читателей представляется книга «Рассказы о математике с примерами на языках Python и C». В книге описаны различные истории или задачи, прямо или косвенно связанные с математикой (магические квадраты, простые числа и пр). Кратко рассмотрены более сложные моменты, например выполнение вычислений с помощью GPU.

Книга распространяется бесплатно, скачать оригинал в PDF можно на странице

.

 

Введение

Как сказал еще Галилей, «Книга природы написана на языке математики», и с этим сложно не согласиться. Математика это универсальный язык науки, это базовые принципы, на которых построена вся Вселенная. 2 + 2 = 4 независимо от того, верим мы в это или нет, знаем мы это или нет, существуем мы вообще или нет, и это будет верно не только для нас, но и для жителя Альфы Центавра.

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

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

Эта книга не задачник, а скорее сборник рассказов о тех или иных математических вопросах. Т. к. математические примеры без цифр бессмысленны, «практическая» часть дается на языках программирования Python и Си.

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

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

Приятного чтения.

Елисеев Дмитрий

История версий текста: 04.2017 - 1.0

 

1. Основы языков Python и Си

 

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

Для использования языка Python нужно установить интерпретатор языка с сайта или воспользоваться онлайн-версией, например на странице . Все примеры из книги работоспособны с любой версией языка Python, 2.7 или 3.

Для запуска программы необходимо:

‐ Сохранить файл в Блокноте с любым именем и расширением .py, например test1.py (удобно также создать папку в корне диска C, например C:\PythonApps).

‐ Открыть консоль (нажать Win+R и набрать cmd), в консоли набрать команду (без кавычек) «python путь_к_файлу.py», например «python C:\PythonApps\test1.py».

Как более удобный вариант, можно скачать бесплатную среду разработки PyСharm community edition, и редактировать и запускать файлы в ней. Скачать PyСharm можно со страницы .

Для запуска программы на языке Си, ее сначала надо сохранить файле с расширением .c, и выполнить команду «gcc имя_файла.c». Будет создан exe-файл, который можно запустить.

Минимальная программа на Си выглядит так:

#include

int main()

{

  printf("Hello world\n");

  return 0;

}

Рассмотрим простые примеры использования.

 

Объявление и вывод переменных

Python: достаточно ввести имя и значение.

x = 3 y = 10

print("x=", x)

print(x + y)

В отличие от языка C++, тип переменной будет определен автоматически, указывать его не нужно. Кстати, его можно узнать, введя print (type(x)).

Cи: необходимо указать тип и значение переменной.

int x = 3;

int y = 10;

printf("x=%d\n", x);

printf("%d\n", x+y);

 

Циклы

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

Python

Вывод чисел от 1 до 9:

for p in range(1, 10):

    print (p)

Вывод чисел от 1 до 9 с шагом 2:

for p in range(1, 10, 2):

    print (p)

Си

Вывод чисел от 1 до 9:

for(int i=1; i<10; i++) {

  printf("%d\n", i);

}

Вывод чисел от 1 до 9 с шагом 2:

for(int i=1; i<10; i+=2) {

  printf("%d\n", i);

}

 

Массивы

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

Python

Объявляем массив чисел:

values = [1, 2, 3, 5, 10, 15, 20]

Добавляем элемент в массив:

values.append(7)

Выводим массив на экран:

print(values)

Выводим элементы массива построчно:

for p in values:

    print(p)

Это же можно сделать с помощью индексов (нумерация элементов массива начинается с 0):

for i in range(0, len(values)):

    print (values[i])

Си: Динамические массивы поддерживаются только в C++, статические массивы создаются так:

int values[7] = { 1,2,3,5,10,15,20 };

for(int i=0; i<7; i++) {

  printf("%d\n", values[i]);

}

При желании можно слегка схитрить, если максимальный размер массива заранее известен.

int values[255] = { 1,2,3,5,10,15,20 }, cnt = 7;

for(int i=0; i

  printf("%d\n", values[i]);

}

values[cnt] = 7;

cnt++;

Можно пользоваться динамическим распределением памяти, хотя это немного сложнее:

int *valuesArray = (int*)malloc(10*sizeof(int));

valuesArray[0] = 1;

valuesArray[1] = 3;

valuesArray[2] = 15;

valuesArray = (int*)realloc(valuesArray, 25*sizeof(int));

valuesArray[20] = 555;

valuesArray[21] = 777;

for(int i=0; i<25; i++) {

  printf("%d\n", valuesArray[i]);

}

free(valuesArray);

Важно заметить, что неинициализированные значения массива, например valuesArray[16], будут содержать «мусор», некие значения которые были до этого в памяти. Си достаточно низкоуровневый язык, и такие моменты нужно учитывать. Хорошим тоном является инициализация всех переменных при их описании. Вот такой код формально не содержит ошибок:

int x;

printf("x=%d\n", x);

Однако при его запуске выведется значение 4196608, или 0, или 32, результат непредсказуем. В большой программе такие ошибки может быть сложно найти, тем более что проявляться они могут не всегда.

 

Арифметические операции

Сложение, умножение,деление:

x1 = 3

x2 = (2 * x1 * x1 + 10*x1 + 7)/x1

Возведение в степень:

x3 = x1**10

print(x1, x2, x3)

Переменную также можно увеличить или уменьшить:

x1 += 1

x1 -= 10

print(x1)

Остаток от деления:

x2 = x1 % 6

print(x2)

Подсчитаем сумму элементов массива:

values = [1,2,3,5,10,15,20]

sum = 0

for p in values:

    sum += p

print(sum)

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

import math

print(math.sqrt(x3))

Условия задаются отступами, аналогично циклам:

print (x1)

if x1 % 2 == 0:

    print("x1 четное число")

else:

    print("x1 нечетное число")

Python может делать вычисления с большими числами, что достаточно удобно:

x1 = 12131231321321312312313131124141

print(10 * x1)

print(math.sqrt(x1))

Можно вывести даже факториал числа 1024, что не сделает ни один калькулятор:

print(math.factorial(1024))

В Си вычисление суммы элементов массива выглядит так:

int sum = 0;

for(int i=0; i

  sum += values[i];

}

printf("Sum=%d\n", sum);

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

 

2. Математические фокусы

 

Для «разминки» рассмотрим несколько фокусов, имеющих отношение к числам. Никаких особых сложностей в них нет, но их знание поможет развеселить или удивить знакомых знанием математики.

 

Умножение в уме числа на 11

Рассмотрим простой пример: 26 * 11 = 286

Сделать это в уме просто, если взять сумму чисел и поместить в середину: 26 * 11 = 2 [2+6] 6

Аналогично 43 * 11 = 473, 71 * 11 = 781 и так далее.

Чуть длиннее расчет, если сумма чисел больше либо равна 10. Но и тогда все просто: в середину кладется младший разряд, а 1 уходит в старший разряд:

47 * 11 = [4] [4 + 7 = 11] [7] = [4 + 1] [1] [7] = 517

94 * 11 = [9] [9 + 4 = 13] [4] = [10] [3] [4] = 1034

 

Возведение в квадрат числа, оканчивающегося на 5

Подсчитать это тоже просто. Если число рассмотреть как пару NM, то первая часть результата — это число N, умноженное на (N + 1), вторая часть числа — всегда 25. 352 = [3 * 4] [25] = 12 25

Аналогично:

252 = [2 * 3] 25 = 625 852= [8*9] 25 = 7225 и так далее.

 

Отгадывание результата

Попросим человека загадать любое число. Например 73. Затем чтобы еще больше запутать отгадывающего, попросим сделать следующие действия:

‐ удвоим число (146)

‐ прибавляем 12 (158)

‐ разделим на 2 (79)

‐ вычтем из результата исходное число (79 - 73 = 6)

В конце мы отгадываем, что результат — 6. Суть в том, что число 6 появляется независимо от того, какое число загадал человек.

Математически, это доказывается очень просто:

(2 * n + 12) / 2 - n = n + 6 - n = 6, независимо от значения n.

 

Отгадывание чисел

Есть другой фокус с отгадыванием чисел. Попросим человека загадать трехзначное число, числа в котором идут в порядке уменьшения (например 752). Попросим человека выполнить следующие действия:

‐ записать число в обратном порядке (257)

‐ вычесть его из исходного числа (752 - 257 = 495)

‐ к ответу добавить его же, только в обратном порядке (495 + 594)

Получится число 1089, которое «фокусник» и объявляет публике.

Математически это тоже несложно доказать.

‐ Любое число вида abc в десятичной системе счисления представляется так:

abc = 100 * a + 10 * b + c.

‐ Разность чисел abc - cba:

100 * a + 10 * b + c + 100 - 100 * c - 10 * b - a = 100 * a - 100 * c - (a - c) = 100 * (a - c) - (a - c)

‐ Т. к. по условию a - c > 0, то результат можно записать в виде:

100 * (a - c) - (a - c) = 100 * (a - c) - 100 + 90 + 10 - (a - c) = 100 * (a - c - 1) + 10 * 9 + (10 - a + c)

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

a1 = a - c - 1, b1 = 9, c1 = 10 - a + c

‐ Добавляем число в обратном порядке:

a1b1c1 + c1b1a1 = 100 * (a - c - 1) + 10 * 9 + (10 - a + c) + 100* (10 - a + c) + 10 * 9 + a - c - 1

Если раскрыть все скобки и сократить лишнее, в остатке будет 1089.

 

3. Число Пи

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

Сегодня достаточно нажать одну кнопку на калькуляторе, чтобы увидеть его значение: Pi = 3,1415926535… Однако, за этими цифрами скрывается многовековая история. Что такое число Пи? Это отношение длины окружности к ее диаметру. То что это константа, не зависящая от самой длины окружности, знали еще в древности. Но чему она равна? Есть ли у этого числа какая-то внутренняя структура, неизвестная закономерность? Узнать это хотели многие. Самый простой и очевидный способ — взять и измерить. Примерно так вероятно и поступали в древности, точность разумеется была невысокой. Еще в древнем Вавилоне значение числа Пи было известно как 25/8. Затем Архимед предложил первый математический метод вычисления числа Пи, с помощью расчета вписанных в круг многоугольников. Это позволяло вычислять значение не «напрямую», с циркулем и линейкой, а математически, что обеспечивало гораздо большую точность. И наконец в 3-м веке нашей эры китайский математик Лю Хуэй придумал первый итерационный алгоритм — алгоритм, в котором число вычисляется не одной формулой, а последовательностью шагов (итераций), где каждая последующая итерация увеличивает точность. С помощью своего метода Лю Хуэй получил Пи с точностью 5 знаков: π = 3,1416. Дальнейшее увеличение точности заняло сотни лет. Математик из Ирана Джамшид ибн Мас‘уд ибн Махмуд Гияс ад-Дин ал-Каши в 15-м веке вычислил число Пи с точностью до 16 знаков, а в 17-м веке голландский математик Лудольф вычислил 32 знака числа Пи. В 19-м веке англичанин Вильям Шенкс, потратив 20 лет, вычислил Пи до 707 знака, однако он так и не узнал, что в 520-м знаке допустил ошибку и все последние годы вычислений оказались напрасны (в итерационных алгоритмах хоть одна ошибка делает все дальнейшие шаги бесполезными).

Что мы знаем о числе Пи сегодня? Действительно, это число весьма интересно:

‐ Число Пи является иррациональным: оно не может быть выражено с помощью дроби вида m/n. Это было доказано только в 1761 году.

‐ Число Пи является трансцендентным: оно не является корнем какого-либо уравнения с целочисленными коэффициентами. Это было доказано в 1882 году.

‐ Число Пи является бесконечным.

‐ Интересное следствие предыдущего пункта: в числе Пи можно найти практически любое число, например свой собственный номер телефона, вопрос лишь в длине последовательности которую придется просмотреть. Можно подтвердить, что так и есть: скачав архив с 10 миллионами знаков числа Пи, я нашел в нем свой номер телефона, номер телефона квартиры где я родился, и номер телефона своей супруги. Но разумеется, никакой «магии» тут нет, лишь теория вероятности. Можно взять любую другую случайную последовательность чисел такой же длины, в ней также найдутся любые заданные числа.

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

Формула Лю-Хуэя (3й век):

Формула Мадхавы-Лейбница (15 век):

Формула Валлиса (17 век):

Формула Мэчина (18 век):

Попробуем вычислить число Пи по второй формуле. Для этого напишем простую программу на языке Python:

sum = 0.0

sign = 1

for p in range(0,33):

    sum += 4.0 * sign / (1 + 2 * p)

    print(p, sum)

    sign = -sign

Запустим программу в любом онлайн-компиляторе языка Питон (например ). Получаем результат:

Шаг  Значение

0    4.0

1    2.666666666666667

2    3.466666666666667

3    2.8952380952380956

4    3.3396825396825403

5    2.9760461760461765

6    3.2837384837384844

7    3.017071817071818

8    3.2523659347188767

9    3.0418396189294032

10   3.232315809405594

11   3.058402765927333

12   3.2184027659273333

13   3.0702546177791854

14   3.208185652261944

15   3.079153394197428

16   3.200365515409549

17   3.0860798011238346

18   3.1941879092319425

19   3.09162380666784

20   3.189184782277596

21   3.0961615264636424

22   3.1850504153525314

23   3.099944032373808

24   3.1815766854350325

25   3.1031453128860127

26   3.1786170109992202

27   3.1058897382719475

28   3.1760651768684385

29   3.108268566698947

30   3.1738423371907505

31   3.110350273698687

32   3.1718887352371485

Как можно видеть, сделав 32 шага алгоритма, мы получили лишь 2 точных знака. Видно, что алгоритм работает, но количество вычислений весьма велико. Как известно, в 15-м веке индийский астроном и математик Мадхава использовал более точную формулу, получив точность числа Пи в 11 знаков:

Попробуем воспроизвести ее в виде программы, чтобы примерно оценить объем вычислений.

Первым шагом необходимо вычислить √12. Возникает резонный вопрос — как это сделать? Оказывается, уже в Вавилоне был известен метод вычисления квадратного корня, который сейчас так и называется «вавилонским». Суть его в вычислении √S по простой формуле:

Здесь x0 — любое приближенное значение, например для √12 можно взять 3.

Запишем формулу в виде программы:

from decimal import Decimal

print ("Квадратный корень:")

number = Decimal(12)

result = Decimal(3)

for p in range(1, 9):

    result = (result + number / result)/Decimal(2)

    difference = result**2 - number

    print (p, result, difference)

sqrt12 = result

Результаты весьма интересны:

Шаг Значение Погрешность
1 3.5 0.25
2 3.464285714285714 0.00127
3 3.464101620029455 3.3890E-8
4 3.464101615137754 2.392873369E-17

Результат: √12 = 3,464101615137754

Как можно видеть, сделав всего 4 шага, можно получить √12 с достаточной точностью, задача вполне посильная даже для ручных расчетов 15 века.

Наконец, запрограммируем вторую часть алгоритма — собственно вычисление Пи.

sum = Decimal(1)

sign = -1

for p in range(1,32):

    sum += Decimal(sign) / Decimal((2 * p + 1)*(3**p))

    sign = -sign

    print(p, sqrt12 * sum)

print("Result:", sqrt12 * sum)

Результаты работы программы:

Шаг  Значение

1    3.079201435678004077382126829

2    3.156181471569954179316680000

3    3.137852891595680345522738769

4    3.142604745663084672802649458

5    3.141308785462883492635401088

6    3.141674312698837671656932680

7    3.141568715941784242161823554

8    3.141599773811505839072149767

9    3.141590510938080099642754230

10   3.141593304503081513121460820

11   3.141592454287646300323593597

12   3.141592715020379765581606212

13   3.141592634547313881242713430

14   3.141592659521713638451335328

15   3.141592651733997585128216671

16   3.141592654172575339199092210

17   3.141592653406165187919674184

18   3.141592653647826046431202391

19   3.141592653571403381773710565

20   3.141592653595634958372427485

21   3.141592653587933449530974820

22   3.141592653590386522717511595

23   3.141592653589603627019680710

24   3.141592653589853940610143646

Уже на 24-м шаге мы получаем искомые 11 знаков числа Пи. Задача явно требовала больше времени чем сейчас, но вполне могла быть решена в средние века.

Современные формулы не столь просты внешне, зато работают еще быстрее. Для примера можно привести формулу Чудновского:

Для сравнения, те же 24 итерации по этой формуле дают число Пи со следующей точностью:

3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249.

Если сделать 100 итераций и вычислить 1000 знаков Пи, то можно увидеть так называемую «точку Фейнмана»:

3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420207

Это последовательность «999999», находящаяся на 762-м знаке от начала. Желающие могут поэкспериментировать дальше самостоятельно с помощью программы на языке Python:

from math import factorial

from decimal import *

def chudnovsky(n):

    pi = Decimal(0)

    k = 0

    while k < n:

        pi += (Decimal(-1)**k) * (Decimal(factorial(6 * k)) / ((factorial(k)**3) * (factorial(3*k))) * (13591409 + 545140134 * k) / (640320**(3 * k)))

        k += 1

        print("Шаг: {} из {}".format(k, n))

    pi = pi * Decimal(10005).sqrt() / 4270934400

    pi = pi**(-1)

    return pi

# Требуемая точность (число знаков)

N = 1000

getcontext().prec = N

val = chudnovsky(N / 14)

print(val)

Эта программа не оптимизирована, и работает довольно-таки медленно, но для ознакомления с сутью алгоритма этого вполне достаточно. Кстати, с помощью формулы Чудновского два инженера Александр Йи и Сингеру Кондо в 2010 году объявили о новом мировом рекорде вычисления Пи на персональном компьютере: 5 трлн знаков после запятой. Компьютеру с 12 ядрами, 97 Гб памяти и 19 жесткими дисками потребовалось 60 дней для выполнения расчетов.

На этом мы закончим с числом Пи, хотя с ним конечно, связано куда больше интересных фактов. Например 3 марта (т. е. 03.14) отмечается международный «день числа Пи», ну а другие факты читатели могут поискать самостоятельно.

 

4. Вычисление радиуса Земли

О том, что Земля круглая сегодня знает каждый школьник, и никого не удивить таким видом планеты из космоса.

Однако в историческом плане, увидеть планету свысока мы смогли совсем-совсем недавно. Как же мог греческий ученый Эратосфен измерить радиус Земли, в 240 году до нашей эры? Оказывается мог, используя 2 научных «инструмента» — транспортир и верблюда, ну и разумеется, математику.

Эратосфен жил в Александрии — крупнейшем городе того времени, центром науки и искусств древнего мира. В Александрии по преданию, находился маяк высотой 120 метров — даже сегодня такое сооружение не просто построить, а в то время маяк считался одним из 7 чудес света. Эратосфен же был не только ученым, но и хранителем Александрийской библиотеки, содержащей до 700000 книг.

Читая труды по географии, Эратосфен нашел интересный факт — в городе Сиене в день летнего солнцестояния Солнце стоит так высоко, что предметы в полдень не отбрасывают тени. Другой может и не обратил бы на это внимание, но Эратосфен не зря интересовался и математикой и астрономией. Он знал что в его городе Александрии тень в этот же день имеет другой угол. Эратосфен дождался солнцестояния, измерил угол солнечных лучей и получил величину 7,2 градуса.

Что это значит? Объяснение данному факту могло быть только одно — Земля круглая, и угол падения солнечных лучей в разных точках Земли в одно время различается.

Картинка с сайта physicsforme.com

Дальше, как говорится, дело техники. Зная примерное расстояние между Сиеном и Александрией (которое было известно из времени в пути каравана верблюдов) и угол, легко получить длину всей окружности. К чести Эратосфена, его результат отличается от сегодняшнего всего лишь на 1%. Так, задолго до эпохи авиации и воздухоплавания, человек впервые смог измерить радиус планеты, даже при этом не отрываясь от нее. Увидеть настоящую кривизну Земли сумели лишь пилоты стратостатов в начале 20 века, более чем через 2000 лет после описанного опыта.

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

 

5. Простые числа

Каждый знает, что простые числа — такие числа, которые делятся только на единицу и самих себя. Но так ли они просты, как кажутся, и актуальны ли сегодня? Попробуем разобраться.

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

1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61 …

Доказательство того, что этих чисел бесконечно много, дал еще Евклид, живший в 300 г. до н. э. Примерно в те же годы уже известный нам греческий математик Эратосфен, придумал довольно-таки простой алгоритм получения простых чисел, суть которого была в последовательном вычеркивании чисел из таблицы. Те оставшиеся числа, которые ни на что не делились, и были простыми. Алгоритм называется «решето Эратосфена» и за счет своей простоты (в нем нет операций умножения или деления, только сложение) используется в компьютерной технике до сих пор.

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

Это несложно записать в виде программы на языке Python:

import math

def is_prime(n):

    if n % 2 == 0 and n > 2:

        return False

    for i in range(3, int(math.sqrt(n)) + 1, 2):

        if n % i == 0:

            return False

    return True

# Вывод всех простых чисел от 1 до N

N = 100

for p in range(1, N, 2):

    if is_prime(p): print(p)

# Вывод результата, является ли заданное число простым

print(is_prime(2147483647))

Желающие могут поэкспериментировать с программой самостоятельно.

Подчиняются ли простые числа каким-либо законам? Да, и они довольно любопытны. Так, например, французский математик Мерсенн еще в 16-м веке обнаружил, что много простых чисел имеет вид 2N - 1, эти числа названы числами Мерсенна. Еще незадолго до этого, в 1588 году, итальянский математик Катальди обнаружил простое число 219 - 1 = 524287 (по классификации Мерсенна оно называется M19). Сегодня это число кажется весьма коротким, однако даже сейчас с калькулятором проверка его простоты заняла бы не один день, а для 16 века это было действительно огромной работой. На 200 лет позже математик Эйлер нашел другое простое число 231 - 1 = 2147483647. Необходимый объем вычислений каждый может представить сам. Он же выдвинул гипотезу, названную позже «проблемой Эйлера», или «бинарной проблемой Гольдбаха»: каждое чётное число, большее двух, можно представить в виде суммы двух простых чисел. Например, можно взять 2 любых четных числа: 123456 и 888777888. С помощью компьютера можно найти их сумму в виде двух простых чисел: 123456 = 61813 + 61643 и 888777888 = 444388979 + 444388909. Интересно здесь то, что точное доказательство этой теоремы не найдено до сих пор, хотя с помощью компьютеров она была проверена до чисел с 18 нулями.

Существует и другая теорема, называемая теоремой Ферма-Эйлера, открытая в 1640 году, которая говорит о том, что если простое число имеет вид 4 * k + 1, то оно может быть представлено в виде суммы квадратов других чисел. Так, например, в нашем примере простое число 444388909 = 4 * 111097227 + 1. И действительно, с помощью компьютера можно найти, что 444388909 = 19197 * 19197 + 8710*8710. Теорема была доказана Эйлером лишь через 100 лет.

И наконец Бернхардом Риманом в 1859 году была выдвинута так называемая «Гипотеза Римана» о количестве распределения простых чисел, не превосходящих некоторое число. Эта гипотеза не доказана до сих пор, она входит в список семи «проблем тысячелетия», за решение каждой из которых Математический институт Клэя в Кембридже готов выплатить награду в один миллион долларов США.

Так что с простыми числами не все так просто. Есть и удивительные факты. Например, в 1883 г. русский математик И. М. Первушин из Пермского уезда доказал простоту числа 261 - 1 = 2305843009213693951. Даже сейчас компьютеру с запущенной вышеприведенной программой требуется несколько минут на проверку данного числа, а на то время это была поистине гигантская работа.

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

Еще одна интересная гипотеза была выдвинута Ферма, который предположил, что все числа вида

являются простыми. Эти числа называются «числами Ферма». Однако, это оказалось верным только для 5 первых чисел: F0 = 3, F1 = 5, F2 = 17, F3 = 257, F4 = 65537. В 1732 году Эйлер опроверг гипотезу, найдя разложение на множители для F5: F5 = 641·6700417.

Существуют ли другие простые числа Ферма, пока неизвестно до сих пор. Такие числа растут очень быстро (для примера, F7 = 340282366920938463463374607431768211457), и их проверка является непростой задачей даже для современных компьютеров.

Актуальны ли простые числа сегодня? Еще как! Простые числа являются основой современной криптографии, так что большинство людей пользуются ими каждый день, даже не задумываясь об этом. Любой процесс аутентификации, например, регистрация телефона в сети, банковские платежи и прочее, требуют криптографических алгоритмов. Суть идеи тут крайне проста и лежит в основе алгоритма RSA, предложенного еще в 1975 году. Отправитель и получатель совместно выбирают так называемый «закрытый ключ», который хранится в надежном месте. Этот ключ представляет собой, как, наверное, читатели уже догадались, простое число. Вторая часть — «открытый ключ», тоже простое число, формируется отправителем и передается в виде произведения вместе с сообщением открытым текстом, его можно опубликовать даже в газете. Суть алгоритма в том, что не зная «закрытой части», получить исходный текст невозможно.

К примеру, если взять два простых числа 444388979 и 444388909, то «закрытым ключом» будет 444388979, а открыто будут передано произведение 197481533549433911 (444388979 * 444388909). Лишь зная вторую половинку, можно вычислить недостающее число и расшифровать им текст.

В чем хитрость? А в том, что произведение двух простых чисел вычислить несложно, а вот обратной операции не существует — если не знать первой части, то такая процедура может быть выполнена лишь перебором. И если взять действительно большие простые числа (например, в 2000 символов длиной), то декодирование их произведения займет несколько лет даже на современном компьютере (к тому времени сообщение станет давно неактуальным). Гениальность данной схемы в том, что в самом алгоритме нет ничего секретного — он открыт и все данные лежат на поверхности (и алгоритм, и таблицы больших простых чисел известны). Сам шифр вместе с открытым ключом можно передавать как угодно, в любом открытом виде. Но не зная секретной части ключа, которую выбрал отправитель, зашифрованный текст мы не получим. Для примера можно сказать, что описание алгоритма RSA было напечатано в журнале в 1977 году, там же был приведен пример шифра. Лишь в 1993 году при помощи распределенных вычислений на компьютерах 600 добровольцев, был получен правильный ответ.

В завершение темы простых чисел, приведем вид так называемой «спирали Улама». Математик Станислав Улам открыл ее случайно в 1963 году, рисуя во время скучного доклада на бумаге числовую спираль и отмечая на ней простые числа:

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

В общем, можно предположить что далеко не все тайны простых чисел раскрыты и до сих пор.

 

6. Совершенные числа

Еще одно удивительное свойство мира чисел было доказано еще Евклидом: если число вида 2p - 1 является простым (уже известное нам число Мерсенна), то число 2P-1(2p - 1) является совершенным, т. е. равно сумме всех его делителей.

Рассмотрим пример для p = 13:

213 - 1 = 8191. Как показывает приведенная ранее программа, 8191 — действительно простое число.

212 * (213 - 1) = 33550336.

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

def is_perfect(n):

    sum = 0

    for i in range(1, int(n / 2) + 1):

        if n % i == 0:

            sum += i

            print(i)

    print("Сумма",sum)

    return sum == n

is_perfect(33550336)

Действительно, 33550336 делится на числа 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8191, 16382, 32764, 65528, 131056, 262112, 524224, 1048448, 2096896, 4193792, 8387584, 16775168. И сумма этих чисел равна искомому 33550336.

Совершенные числа встречаются довольно-таки редко, их последовательность согласно Википедии, образует вид:

6,

28,

496,

8128,

33 550 336,

8 589 869 056,

137 438 691 328,

2 305 843 008 139 952 128,

2 658 455 991 569 831 744 654 692 615 953 842 176,

Кстати, еще Эйлер доказал, что все совершенные числа имеют только вид 2p-1(2p - 1). А вот нечетных совершенных чисел пока не обнаружено, но и не доказано что их не существует. Интересно проверить этот факт практически. Совершенное число 137438691328 обнаружил еще немецкий математик Иоганн Мюллер в 16-м веке. Сегодня такое число несложно проверить на компьютере.

Во-первых, слегка оптимизируем приведенную выше программу. Как нетрудно видеть, если число N делится нацело на P, то мы «автоматом» сразу находим и второй делитель N/P. Например, если 10 делится нацело на 2, то оно делится и на 10 / 2 = 5. Это позволяет заметно сократить число вариантов перебора. Во-вторых, используем тип чисел Decimal, позволяющий использовать большие числа. Обновленная программа выглядит так:

from decimal import *

def is_perfect(n):

    s = Decimal(1)

    p = Decimal(2)

    while p < n.sqrt()+1:

        if n % p == 0:

            s += p

            if p != n/p: s += n/p

        p += 1

    return s == n

print(is_perfect(Decimal('137438691328')))

Запускаем, программа работает — число '137438691328' действительно является совершенным. Оно делится на 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524287, 1048574, 2097148, 4194296, 8388592, 16777184, 33554368, 67108736, 134217472, 268434944, 536869888, 1073739776, 2147479552, 4294959104, 8589918208, 17179836416, 34359672832 и 68719345664, сумма этих чисел равна 137438691328. Однако, на моем компьютере проверка «совершенности» данного числа заняла… 54 секунды. Это конечно быстро по сравнению с 16-м веком, но совершенно недостаточно чтобы проверить все числа, хотя бы до миллиарда. Значит пора использовать более тяжелую артиллерию — перепишем программу на языке Си. Все-таки Python это интерпретатор, и работает заметно медленнее. Получаемый код не намного сложнее:

#include

#include

#include

#include

bool isPerfect(unsigned long long int n)

{

  unsigned long long int sum = 1, i;

  for(i=2; i<=sqrt(n)+1; i++)

  {

    if (n%i==0) {

      sum += i;

      if (i != n/i) {

        sum += n/i;

      }

    }

  }

  return sum == n;

}

int main()

{

  unsigned long long int n = 137438691328LL;

  bool res = isPerfect(n);

  printf("%d\n", res);

  return 0;

}

Компилируем программу с помощью компилятора gcc, запускаем получившийся exe-файл: время выполнения меньше секунды, уже гораздо лучше. Теперь несложно поменять функцию main для перебора всех чисел от 1 до 200000000000. В код также добавлен вывод промежуточных результатов каждые 1000000 итераций, чтобы видеть ход выполнения программы.

int main()

{

  unsigned long long int MAX = 200000000000LL;

  unsigned long long int p;

  for (p=1; p

    if (isPerfect(p))

      printf(" %llu ", p);

        if (p % 1000000 == 0)

          printf("*%llu,%llu*", 100*p/MAX, p);

  }

}

Увы, прогноз относительно скорости расчетов оказался слишком оптимистичным. Примерно за час работы программы, было перебрано лишь 100 млн. вариантов, а для перебора всех 200 млрд. понадобился бы не один день. Желающие могут продолжить процесс самостоятельно, однако с уверенностью можно сказать что в диапазоне от 1 до 100000000 действительно нет совершенных чисел кроме 6, 28, 496, 8128 и 33550336.

Проверка числа 2 305 843 008 139 952 128 является непростой задачей даже для современного домашнего компьютера — во-первых, в языке C/C++ нет встроенных типов данных для столь большого числа, а во-вторых, число вариантов перебора весьма велико.

Разумеется, выше было приведено самое простое решение «в лоб», можно оптимизировать и саму программу, например разбить вычисление на несколько процессорных ядер, однако данная задача выходит за рамки этого материала. Немного про параллельные вычисления будет рассказано в конце книги.

 

7. Магический квадрат

Еще одна старинная математическая головоломка — магический квадрат. Магическим называют квадрат, заполненный неповторяющимися числами так, что суммы чисел по горизонталям, вертикалям и диагоналям одинаковы. Такие квадраты известны давно, самым старым из известных является магический квадрат Ло Шу, изображенный в Китае в 2200 г. до нашей эры. Если подсчитать количество точек, то можно перевести квадрат в современный вид, изображенный справа.

Магический квадрат 4х4 был обнаружен в индийских надписях 11 века:

И наконец, известный квадрат 4х4, изображенный на гравюре немецкого художника Дюрера «Меланхолия». Этот квадрат изображен не просто так, 2 числа 1514 указывают на дату создания гравюры.