К книге
Рассказы о математике с примерами на языках Python и CСтраница 5
29%
Страница 5
5

К счастью для нас, язык Python может работать с большими числами неограниченной длины, ну и мощность современных компьютеров достаточно велика. Сама проверка этого числа методом Люка-Лемера заняла всего лишь 15с, разумеется, для компьютеров и программистов в 1978г это было гораздо более сложной задачей.

Изучение чисел Мерсенна продолжается энтузиастами и сейчас. Самым большим на момент написания книги простым числом, является число М49 = 274207281 - 1, содержащее 22 338 618 знаков. А в начале 2018 года было найдено 50е число Мерсенна М50 = 277232917-1. Его проверка заняла на 32-ядерном сервере Xeon 82 часа. Существует даже сеть распределенных вычислений https://www.mersenne.org, присоединиться к которой может любой желающий, для этого достаточно установить на компьютер программу для расчета.

Кстати, вывести число 277232917-1 совсем несложно с помощью Python. Само вычисление числа проблем не составляет, а вот стандартная функция print для таких длинных чисел не работает - результата дождаться так и не удалось. Однако, число можно вывести посимвольно, если каждый раз выводить остаток от деления на 10, и уменьшать число во столько же раз.

bp2 = 2**77232917 - 1

while True:

print(bp2%10, end='')

bp2 = bp2/10

if bp2 == 0: break

Разумеется, только вывод на экран таким способом займет порядка нескольких часов. Второй недостаток программы в том, что число выводится задом наперед, т.к. первыми выводятся самые младшие разряды, после вывода данные в файле придется перевернуть. Зато мы все-таки можем посмотреть, как выглядит самое больше найденное число Мерсенна (в перевернутом виде) в современной истории: 1709712679608160372856311737129564169399997264989729636740737…

Впрочем, все это уже сделали за нас, уже готовое число M50 можно просто скачать по ссылке https://www.mersenne.ca/primes/digits/M77232917.txt, размер файла составляет 23Мбайт.

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

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

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

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 <string.h>

#include <math.h>

#include <stdbool.h>

#include <stdint.h>

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<MAX; p++) {

if (isPerfect(p))

printf(" %llu ", p);

      if (p % 1000000 == 0)

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

}

}

Увы, для таких чисел как 200000000000, скорости обычного компьютера все равно недостаточно. Примерно за час работы программы, было перебрано лишь 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 указывают на дату создания гравюры.

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

Сумма чисел магического квадрата размера NxN зависит только от N, и определяется формулой:

Это несложно доказать, т.к. сумма всех чисел квадрата равна сумме ряда 1..N2. Действительно, для квадрата Дюрера M(4) = 34, что можно посчитать на картине. Для квадратов разной размерности суммы равны соответственно: M(3) = 15, M(4) = 34, M(5) = 65, M(6) = 111, M(7) = 175, M(8) = 260, M(9) = 369, M(10) = 505.

Напишем программу для построения магических квадратов размерности N. Первый подход будет “в лоб”, напрямую. Создадим массив, содержащий все числа от 1 до N2 и получим все возможные перестановки этого массива. Их число довольно-таки велико, и составляет 1*2*..*N = N! вариантов. Также для каждого массива необходимо проверить, является ли он “магическим”, т.е. выполняется ли требование равенства сумм.

Для получения всех перестановок воспользуемся алгоритмом, описанным здесь - https://prog-cpp.ru/permutation/.

Код программы приведен ниже:

def swap(arr, i, j):

arr[i], arr[j] = arr[j], arr[i]

def next_set(arr, n):

j = n - 2

while j != -1 and arr[j] >= arr[j + 1]: j -= 1

if j == -1:

return False

k = n - 1

while arr[j] >= arr[k]: k -= 1

swap(arr, j, k)

l = j + 1

r = n - 1

while l < r:

swap(arr, l, r)

l += 1

r -= 1

return True

def is_magic(arr, n):

for i in range(0, n):

sum1, sum2, sum3, sum4 = 0, 0, 0, 0

for j in range(0, n):

sum1 += arr[i * n + j]

sum2 += arr[j * n + i]

sum3 += arr[j * n + j]

sum4 += arr[(n - j - 1) * n + j]

if sum1 != sum2 or sum1 != sum3 or sum1 != sum4 or

sum2 != sum3 or sum2 != sum4 or sum3 != sum4:

return False

return True

def show_squares(n):

N = n * n

arr = [i + 1 for i in range(N)]

cnt = 0

while next_set(arr, N):

if is_magic(arr, n):

print(arr)

cnt += 1

return cnt

# Требуемая размерность

cnt = show_squares(3)

print("Число вариантов:", cnt)

Программа выдала 8 вариантов для N=3, время вычисления составило 2 секунды:

[2, 7, 6, 9, 5, 1, 4, 3, 8]      [6, 1, 8, 7, 5, 3, 2, 9, 4]

      [2, 9, 4, 7, 5, 3, 6, 1, 8]      [6, 7, 2, 1, 5, 9, 8, 3, 4]

      [4, 3, 8, 9, 5, 1, 2, 7, 6]      [8, 1, 6, 3, 5, 7, 4, 9, 2]

      [4, 9, 2, 3, 5, 7, 8, 1, 6]      [8, 3, 4, 1, 5, 9, 6, 7, 2]

Действительно, как известно, существует только 1 магический квадрат 3х3:

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

Теперь попробуем вывести квадраты 4х4. Запускаем программу… и ничего не видим. Как было сказано выше, число вариантов перебора для 16 цифр равняется 16! или 20922789888000 вариантов. На моем компьютере полный перебор такого количества занял бы 1089 дней!

Однако посмотрим на магический квадрат еще раз:

Суммы всех элементов по горизонтали и вертикали равны. Из этого мы легко можем записать равенство его членов:

x11 + x12 + x13 + x14 = x21 + x22 + x23 + x24

Предыдущая главаГлава 5 из 17Следующая глава