Задачі на
довгу арифметику h2>
Розглянемо
досить популярну в програмуванні завдання на роботу з
"довгими" числами. Реально з "астрономічними" або
"мікроскопічними" числами доводиться стикатися не так вже й
часто. Тим не менше, вправи, що розглядаються в цій публікації, можуть
послужити хорошою тренуванням в області програмування і зайняти гідне
місце в класах з поглибленим вивченням інформатики або на гуртках з програмування.
Алгоритми, представлені нижче, записані на Turbo Pascal, версія7.0. При
бажанні або необхідності вони можуть легко бути адаптовані до будь-якої іншої
програмному середовищі. p>
Діапазон
подання цілих чисел (Integer, Word, LongInt) обмежений, про що не раз вже
говорилося (втім, для дійсних величин це зауваження теж актуально).
Тому при вирішенні завдань завжди доводиться діяти з осторогою, - як би не
допустити виникнення помилки виходу за діапазон або переповнення. Наприклад,
обчислюючи факторіал (n! = 1 * 2 * 3 * ... * n), в діапазоні подання величин типу
Integer вдасться правильно отримати тільки 7! = 5040, а в діапазоні подання
типу LongInt - 12! = 479001600. Для більших значень, звичайно, можна використовувати
дійсні типи даних, але це вже не гарантує точного результату.
Тому корисно для отримання точних значень при дії з багатозначними
числами розробити інші способи представлення таких чисел, алгоритми
виконання арифметичних та інших операцій, процедури введення та виведення
результатів і т.д. p>
Покажемо
реалізацію рішення такого роду завдань на прикладі множення одного багатозначного
числа на інше. Саме ця арифметична операція найбільш часто використовується
при вирішенні інших завдань. p>
Найбільш
природним способом представлення багатозначного числа є запис кожного
його розряду у вигляді окремого елемента лінійного масиву (або списку, де
пам'ять під цифру буде приділятися в міру потреби, в той час як в масиві
доводиться наперед задавати максимальну кількість елементів у ньому). Нехай
(для зручності подальших дій) розряди "довгого" числа при
записи в масив нумеруються з одиниці, починаючи з розряду одиниць, тобто фігура з
розряду одиниць - елемент масиву з номером один, цифра з розряду десятків - елемент
масиву з номером два і т.д. Визначимо для роботи з "довгими"
невід'ємними числами тип даних: p>
Const MNax = 2000; p>
Type
Digit = 0 .. 9; p>
DlChislo = Array [1 .. Nmax] Of Digit; p>
Для вирішення
поставленого завдання необхідно вміти виконувати наступні дії: p>
1) введення
"довгого" числа; p>
2) власне
множення двох "довгих" чисел; p>
3) висновок
"довгого" числа; p>
4) визначення
кількості цифр у записі числа. p>
підзадач реалізуємо у вигляді окремої підпрограми. Почнемо з введення. Ввести велике
число доцільно у вигляді рядка, а в подальшому перетворити в масив цифр.
У процедурі врахований вказаний вище спосіб розміщення "довгого" числа в
масиві, тобто з точки зору користувача число записується як би у зворотному
порядку. p>
(Процедура перетворення
довгого числа, записаного p>
у вигляді рядка, в масив цифр; мінлива OK
приймає значення True, p>
якщо в запису числа немає сторонніх символів,
відмінних від десяткових p>
цифр, інакше - false) p>
Procedure Translate (S: String; Var
A: DlChislo; Var OK: Boolean); p>
Var I: Word; p>
Begin p>
Zero (A); I
: = Length (S); OK: = True; p>
While (I
> = 1) And OK Do p>
Begin p>
If
S [I] In ['0 '.. '9'] p>
Then
A [Length (S) - I + 1]: = Ord (S [I]) - 48 p>
Else
OK: = False; I: = I - 1 p>
End p>
End; p>
У процедурі
викликається підпрограма Zero (A), призначення якої - запис нуля в кожен
розряд довгого числа. Ось текст цієї процедури: p>
(Процедура обнулення довгого числа) p>
Procedure Zero (Var A: DlChislo); p>
Var I:
Integer; p>
Begin p>
For I
: = 1 To NMax Do A [I]: = 0; p>
End; p>
Таким чином,
довге число записано в масив, де попереду (як елементи з великими
номерами) стоять незначущий нулі. При виконанні дій і виведення відповіді вони не
враховуються. p>
Зараз
розробимо функцію визначення кількості значущих цифр у записі числа,
оскільки вона буде потрібно при реалізації підпрограми множення. p>
(Функція визначення кількості цифр у
запису довгого числа) p>
Function Dlina (C: DlChislo):
Integer; p>
Var I:
Integer; p>
Begin p>
I: =
NMax; p>
While (I
> 1) And (C [I] = 0) Do I: = I - 1; p>
Dlina: =
I p>
End; p>
При її
розробці було використане таке міркування: якщо кількість не дорівнює нулю,
то кількість цифр у його записи одно номером перші цифри, відмінною від нуля,
якщо перегляд числа здійснюється від старшого розряду до молодшого. Якщо ж
довге число дорівнює нулю, то виходить, що кількість цифр у його записи одно
одній, що і було потрібно. p>
Ну і, нарешті,
головна процедура, заради якої і була зроблена вся попередня робота. При
складанні алгоритму використовується ідея множення "стовпчиком", хоча в
нашому варіанті складання виконується не після закінчення множення, а по ходу його,
тобто перемноживши чергові цифри, відразу ж додаємо результуючу цифру в
потрібний розряд і формуємо перенесення в наступний розряд. p>
(Процедура
множення довгих чисел. p>
A, B - множники, C - твір) p>
Procedure Multiplication (A, B:
DlChislo; Var C: DlChislo); p>
Var I, J:
Integer; P: Digit; VspRez: 0 .. 99; p>
Begin p>
Zero (C); p>
For I: = 1
To Dlina (A) Do (Цикл за кількістю цифр p>
в першому числі) p>
Begin p>
P: = 0; (Спочатку перенесення дорівнює
нулю) p>
For J: = 1 To Dlina (B) Do (Цикл по
кількістю цифр p>
в другому
числі) p>
Begin p>
VspRez: = A [I] * B [J] + P + C [I + J - 1]; p>
C [I + J - 1]: = VspRez Mod 10; (Чергове
значення цифри в p>
розряді I + J - 1) p>
P: = VspRez Div 10 (Перенесення в наступний розряд) p>
End; p>
C [I + J]: = P (останній перенесення може
бути відмінний від нуля, p>
запишемо його в поки що
вільний розряд) p>
End p>
End; p>
Зараз приведемо
лістинг програми цілком. p>
Program DlUmn; p>
Const NMax = 2000; p>
Type Digit = 0 .. 9; DlChislo = Array [1 .. Nmax] Of Digit; p>
Var S: String; p>
M, N, R, F
: DlChislo; p>
I, MaxF:
Word; p>
Logic: Boolean; p>
(Процедура
обнулення довгого числа) p>
Procedure Zero (Var A: DlChislo); p>
Var I: Integer; p>
Begin p>
For I: = 1
To NMax Do A [I]: = 0; p>
End; p>
(Функція
визначення кількості цифр у записі довгого числа) p>
Function Dlina (C: DlChislo): Integer; p>
Var I: Integer; p>
Begin p>
I: = NMax; p>
While (I
> 1) And (C [I] = 0) Do I: = I - 1; p>
Dlina: = I p>
End; p>
(Процедура
друку довгого числа) p>
Procedure Print (A: DlChislo); p>
Var I: Integer; p>
Begin p>
For I: =
Dlina (A) DownTo 1 Do Write (A [I]: 1); p>
WriteLn p>
End; p>
(Процедура
перетворення довгого числа в масив цифр) p>
Procedure Translate (S: String; Var A: DlChislo; p>
Var OK: Boolean); p>
Var I: Word; p>
Begin p>
Zero (A); I
: = Length (S); OK: = True; p>
While (I
> = 1) And OK Do p>
Begin p>
If S [I]
In ['0 '.. '9'] p>
Then
A [Length (S) - I + 1]: = Ord (S [I]) - 48 p>
Else OK: =
False; p>
I: = I --
1 p>
End p>
End; p>
Procedure Multiplication (A, B: DlChislo; Var C:
DlChislo); p>
Var I, J: Integer; P: Digit; VspRez: 0 .. 99; p>
Begin p>
Zero (C); p>
For I: = 1 To
Dlina (A) Do p>
Begin P: = 0; p>
For J
: = 1 To Dlina (B) Do p>
Begin p>
VspRez: = A [I] * B [J] + P + C [I + J - 1]; p>
C [I + J - 1]: = VspRez Mod 10; p>
P: = VspRez Div 10 p>
End; p>
C [I +
J]: = P p>
End p>
End; p>
(Основна
програма) p>
Begin p>
Repeat (повторюємо введення, поки число не буде
введено правильно) p>
Write ( 'Введіть перший множник:'); p>
ReadLn (S); Translate (S, M, Logic) p>
Until Logic; p>
Repeat p>
Write ( 'Введіть другий множник:'); p>
ReadLn (S); Translate (S, N, Logic) p>
Until Logic; p>
Multiplication (M, N, R); Print (R) p>
End. p>
У наведеному
лістингу Print - процедура виведення довгого числа. Надамо читачеві
самостійно розібратися в алгоритмі її роботи. p>
Повернемося до
обчислення факторіалу. Використовуючи розроблені підпрограми, визначимо,
значення факторіалу якого максимального числа можна розмістити в пам'яті при
таке подання довгих чисел. p>
Ось змінений
фрагмент основної програми, що вирішує поставлене завдання. p>
Begin p>
MaxF: =
810; p>
Zero (F); p>
F [1]: = 1; p>
For I: = 1
To MaxF Do p>
Begin p>
Str (I, S); (перетворення числа I до
строковому типу S) p>
Translate (S, M,
Logic); p>
Multiplication (F, M, F); p>
Print (F); p>
WriteLn ( 'Факторіал числа', I: 4, 'містить', Dlina (F), 'цифр .') p>
End p>
End. p>
Розрахунки
показали, що можна обчислювати факторіали до значення 810! включно, в
запису якого 1999 цифр. Далі знову виникає переповнення. Розрахунки за
програмі тривають близько 5 хвилин (IBM PC з процесором Pentium-100). p>
Нижче буде
запропонований список завдань для самостійного виконання. З них, на думку
автора, найбільшу складність представляють реалізації алгоритмів поділу одного
довгого числа на інше і витяг квадратного кореня. Алгоритм добування
квадратного кореня докладно описаний у довіднику В.А. Гусєва та А.Г. Мордковіча
[7]. У деяких випадках складені програми можуть виступати як
підпрограми при розробці алгоритмів вирішення інших, більш складних (як у
прикладі з факторіали), завдань. Крім авторських задач і завдань зі списку
літератури тут наведені завдання з олімпіад школярів з програмування,
що проводилися в Пермської області в 1989-99гг. p>
Завдання для
самостійного рішення p>
Скласти
програму порівняння двох багатозначних чисел (кількість знаків у записі чисел
более20). p>
Скласти
програму, що підсумовує два натуральних багатозначних числа з кількістю знаків
более20. p>
Скласти
програму обчислення ступеня an, якщо a> MaxInt, n> 10. p>
Скласти
програму обчислення числа 264 - 1, в результаті зберегти всі цифри. p>
Скласти
програму обчислення 100!. p>
Скласти програму
витягу точного квадратного кореня з n-розрядного числа (n> 40). p>
Скласти
програму обчислення точного значення n!, де n> 12. p>
Скласти
програму обчислення точного значення nn, де n> 10. p>
Скласти
програму ділення числа a на число b, якщо a, b - багатозначні числа. p>
Обчислити 100!
+ 2100. p>
Обчислити 100!
- 2100. p>
Обчислити 7123.
p>
Зустрічаються чи
серед цифр числа 211213 - 1 дві поспіль йдуть дев'ятки? p>
Обчислити
2-200. p>
Скласти
програму знаходження приватного та залишку від ділення m-значного числа на
n-значне (m, n> 20). p>
З'ясувати, яке
з чисел am, bn більше і на скільки (a, b 20). p>
Обчислити
точне значення (n!)! (n> = 3). p>
Скласти
програму обчислення точного значення суми 1! + 2! + 3! + ... + N! при
n> 10. p>
Скласти
програму обчислення точного значення суми дробів p>
p>
при n> 10.
Відповідь має бути представлений у вигляді несократімой дробу P/Q, де P, Q --
натуральні числа. p>
Обчислити
точне значення (nn)! при n> = 3. p>
Скласти
програму обчислення точного значення суми перших n членів послідовності
1, k, k2, k3, ..., kn (n> MaxInt). Вказівка: використовуйте формулу суми n
членів геометричної прогресії. p>
Скласти
програму обчислення точного значення суми перших n членів послідовності
чисел, кратних даного натурального числа k (n> MaxInt). Вказівка:
використовуйте формулу суми n членів арифметичної прогресії. p>
Обчислити
точне значення суми 12 + 22 + 32 + ... + n2 (n> = 20000). p>
Обчислити
точне значення суми 1n + 2n + 3n + ... + Nn (n> = 10). p>
Знайти першу
просте число, яке більше 1011. p>
Скласти
програму обчислення точного значення многочлена anxn + an - 1xn - 1 + ... +
a1x + a0, де aiіx - цілі числа більше 1011. p>
Знайти
найбільший спільний дільник і найменше спільне кратне чисел m і n (m,
n> = 1011). p>
Перевірити,
чи є числа m і n (m, n> = 1011) взаємно простими. p>
Доведіть, що
число 219936 * (219937 - 1) є досконалим, тобто дорівнює сумі всіх своїх
дільників, крім самого себе. p>
"Обертове
число ". Написати програму, яка знаходить число, що володіє наступними
властивостями: p>
1) число
закінчується на 5; p>
2) при
примноження його на 5 утворюється нове число, яке може бути отримано з
вихідного викресленням цифри 5 на кінці і переписуванням її в початок числа. p>
33. Дана
послідовність, задана рекурентних формулою p>
an + 1 = 7an mod 2023, a1 = 1, p>
де x mod y
означає залишок від розподілу x на y. Написати програму, яка обчислює an при
1