"Довга"
арифметика h2>
Відомо, що
арифметичні дії, що виконуються комп'ютером в обмеженій кількості розрядів,
не завжди дозволяють отримати точний результат. Більше того, ми обмежені
розміром (величиною) чисел, з якими можемо працювати. А якщо нам необхідно
виконати арифметичні дії над дуже великими числами, наприклад, p>
30! =
265252859812191058636308480000000? P>
У таких випадках
ми самі повинні подбати про представлення чисел в машині і про точний
виконання арифметичних операцій над ними. p>
Числа, для
представлення яких в стандартних комп'ютерних типах даних не вистачає
кількості двійкових розрядів, називаються "довгими". Реалізація
арифметичних операцій над такими "довгими" числами отримала
назва "довгої арифметики". p>
Організація
роботи з "довгими" числами багато в чому залежить від того, як ми
наведемо у комп'ютері ці числа. "Довгий" число можна записати,
наприклад, за допомогою масиву десяткових цифр, кількість елементів у такому
масиві дорівнює кількості значущих цифр у "довгому" числі. Але якщо ми
будемо реалізовувати арифметичні операції над цим числом, то розмір масиву
повинен бути достатнім, щоб розмістити в ньому і результат, наприклад,
множення. p>
Існують і
інші вистави "довгих" чисел. Розглянемо одне з них.
Уявімо наше число p>
30! =
265252859812191058636308480000000 p>
у вигляді: p>
30! = 2 *
(104) 8 + 6525 * (104) 7 + 2859 * (104) + 8121 * (104) 5 + 9105 * (104) 4 + 8636 *
(104) 3 + 3084 * (104) 2 + 8000 * (104) 1 + 0000 * (104) 0. P>
Це
подання наштовхує на думку про масиві, представленому в табл. 1. p>
Таблиця 1 p>
Номер
елемента в масиві А p>
0 p>
1 p>
2 p>
3 p>
4 p>
5 p>
6 p>
7 p>
8 p>
9 p>
Значення p>
9 p>
0 p>
8000 p>
3084 p>
8636 p>
9105 p>
8121 p>
2859 p>
6525 p>
2 p>
Ми можемо
вважати, що наше "довге" число представлено в 10000-10 системі
числення (десятитисячне-десяткова система числення, приведіть аналогію з
вісімкове-десяткового системою числення), а "цифрами" числа
є чотиризначні числа. p>
Виникають
питання. Що за 9 в А [0], чому число зберігається "задом наперед"?
Відповіді очевидні, але почекаємо з передчасними поясненнями. Відповіді на питання
будуть ясні з тексту. p>
Примітка. Ми
працюємо з позитивними числами! p>
Перше завдання.
Ввести "довге" число з файлу. Рішення задачі почнемо з опису
даних. p>
Const MaxDig = 1000; (Максимальне
кількість цифр - чотиризначних!) p>
Osn = 10000; (Основа нашої системи
числення, p>
в
елементах масиву зберігаємо чотиризначні числа) p>
Type Tlong
= Array [0 .. MaxDig] Of Integer; p>
(Максимальна кількість десяткових цифр у
нашому числі) p>
Алгоритм введення
"довгого" числа з файлу розглянемо на конкретному прикладі. p>
Нехай у файлі
записано число 23851674 і підставою (Osn) є 1000 (зберігаємо по три цифри в
елементі масиву А). Зміна значень елементів масиву А в процесі введення
(посимвольний в змінну Ch) показано в табл. 2. P>
Таблиця 2 p>
А [0] p>
А [1] p>
А [2] p>
А [3] p>
Ch p>
Примітка p>
3 p>
674 p>
851 p>
23 p>
- p>
Кінцеве
стан p>
0 p>
0 p>
0 p>
0 p>
2 p>
Початкове
стан p>
1 p>
2 p>
0 p>
0 p>
3 p>
1-й крок p>
1 p>
23 p>
0 p>
0 p>
8 p>
2-й крок p>
1 p>
238 p>
0 p>
0 p>
5 p>
3-й крок p>
2 p>
385 p>
2 p>
0 p>
1 p>
4-й
крок: старша цифра елемента А [1] перейшла в поки "порожній" елемент
А [2] p>
2 p>
851 p>
23 p>
0 p>
6 p>
5-й крок p>
2 p>
516 p>
238 p>
0 p>
7 p>
6-й крок p>
3 p>
167 p>
385 p>
2 p>
4 p>
7-й крок p>
3 p>
674 p>
851 p>
23 p>
Проаналізуємо
таблицю (і отримаємо відповіді на поставлені вище питання). p>
1. В А [0]
зберігаємо кількість задіяних (ненульових) елементів масиву А - це вже
очевидно. p>
2. При
обробці кожної чергової цифри вхідного числа старша цифра елемента масиву
з номером i стає молодшою цифрою числа в елементі i +1, а сума, що вводиться цифра
буде молодшою цифрою числа з А [1]. У результаті роботи нашого алгоритму ми
отримали число, записане "задом наперед". p>
Примітка
(методичне): Можна обмежитися цим поясненням і розробку процедури
винести на самостійне завдання. Можна продовжити пояснення. Наприклад,
виписати фрагмент тексту процедури перенесення старшої цифри з A [i] в молодшу
цифру А [i +1], тобто зрушення вже введеної частини числа на одну позицію вправо: p>
For i: = A [0] DownTo 1 Do p>
Begin p>
A [i + l]: = A [i + l] + (Longint (A [i]) *
10) Div Osn; p>
A [i]: = (LongInt (A [i]) * 10) Mod
Osn; p>
End; p>
Нехай ми вводимо
число 23851674 і перші 6 цифр вже розмістили "задом наперед" в
масиві А. У символьну змінну вважали чергову цифру "довгого"
числа - це "7". На нашу алгоритму ця цифра "7" повинна
бути розміщена молодшою цифрою в А [1]. Виписаний фрагмент програми
"звільняє" місце для цієї цифри. У таблиці показані результати
роботи цього фрагмента. p>
i p>
А [1] p>
А [2] p>
А [3] p>
ch p>
2 p>
516 p>
238 p>
0 p>
7 p>
2 p>
516 p>
380 p>
2 p>
1 p>
160 p>
385 p>
2 p>
Після цього
залишається тільки додати поточну (зчитану в ch) цифру "довгого"
числа до А [1] і змінити значення А [0]. p>
У кінцевому
підсумку процедура повинна мати такий вигляд: p>
Procedure ReadLong (Var A: Tlong); p>
Var ch: char;
i: Integer; p>
Begin p>
FillChar (A, SizeOf (A), 0); p>
Read (ch); p>
While Not (ch In ['0 '.. '9']) Do
Read (ch); p>
(пропуск не цифр у вхідному файлі) p>
While
ch In ['0 '.. '9'] Do p>
Begin p>
For i: = A [0] DownTo 1 Do p>
Begin p>
( "протягування" старшої
цифри в числі з A [i] p>
в молодшу цифру числа з A [i + l]) p>
A [i + l]: = A [i + l] +
(LongInt (A [i]) * 10) Div Osn; p>
A [i]
: = (LongInt (A [i]) * 10) Mod Osn p>
End; p>
A [1]: = A [l] + Ord (ch) - Ord ('0'); p>
(додаємо молодшу цифру до числа з А [1]) p>
If
A [A [0] +1]> 0 Then Inc (A [0 ]); p>
(змінюємо довжину, кількість задіяних елементів масиву А) p>
Read (ch) p>
End p>
End; p>
Друге завдання.
Висновок "довгого" числа у файл чи на екран. P>
Здавалося б,
немає проблем - виводь число за числом. Однак у силу обраного нами
вистави "довгого" числа ми повинні завжди пам'ятати, що в кожному
елементі масиву зберігається не послідовність цифр "довгого"
числа, а значення числа, записаного цими цифрами. Нехай в елементах масиву
зберігаються чотиризначні числа. Тоді "довге" число 128400583274
буде в масиві А представлено наступним чином: p>
A [0] p>
A [1] p>
A [2] p>
A [3] p>
3 p>
3274 p>
58 p>
1284 p>
При виведенні
"довгого" числа з масиву нам необхідно вивести 0058, інакше буде
втрата цифр. Отже, незначущий нулі також необхідно виводити. Процедура виводу
має вигляд: p>
Procedure WriteLong (Const A:
Tlong); p>
Var ls, s: String; i: Integer; p>
Begin p>
Str (Osn Div 10, Is); p>
Write (A [A [0]]; (виводимо старші цифри числа) p>
For i
: = A [0] - l DownTo 1 Do p>
Begin p>
Str (A [i], s); p>
While Length (s)
(доповнюємо незначущий нулями) p>
Write (s) p>
End; p>
WriteLn p>
End; p>
Третє завдання.
Попередня робота по опису способу зберігання, введення і виведення "довгих"
чисел виконана. p>
У нас є все
необхідні "цеглинки", наприклад, для написання програми складання
двох "довгих" позитивних чисел. Вихідні числа і результат зберігаємо
у файлах. Назвемо процедуру складання SumLongTwo. P>
Тоді програма
введення двох "довгих" чисел та виведення результату їх складання буде
мати такий вигляд: p>
Var A, B, C: Tlong; p>
Begin p>
Assign (Input,
'Input.txt'); Reset (Input); p>
ReadLong (A); ReadLong (B); p>
Close (Input); p>
SumLongTwo (A, B, C); p>
Assign (Output, 'Output.txt'); p>
Rewrite (Output); p>
WriteLong (C); p>
Close (Output) p>
End. p>
Алгоритм
процедури складання можна пояснити на простому прикладі. Нехай А = 870613029451,
В = 3475912100517461. P>
i p>
A [i] p>
B [i] p>
C [1] p>
C [2] p>
C [3] p>
C [4] p>
1 p>
9451 p>
7461 p>
6912 p>
1 p>
0 p>
0 p>
2 p>
1302 p>
51 p>
6912 p>
1354 p>
0 p>
0 p>
3 p>
8706 p>
9121 p>
6912 p>
1354 p>
7827 p>
1 p>
4 p>
0 p>
3475 p>
6912 p>
1354 p>
7827 p>
3476 p>
Алгоритм
імітує звичне додавання стовпчиком, починаючи з молодших розрядів. І саме
для простоти реалізації арифметичних операцій над "довгими"
числами використовується машинне подання "задом наперед". p>
Результат: С =
3476782713546912. P>
Нижче наведено
текст процедури складання двох "довгих" чисел. p>
Procedure SumLongTwo (A, B: Nlong;
Var C: Tlong); p>
Var i, k:
Integer; p>
Begin p>
FillChar (C, SizeOf (C), 0); p>
If A [0]> B [0] Then k: = A [0]
Else k: = B [0]; p>
For i: = l To k Do p>
Begin З [i +1]
: = (C [i] + A [i] + B [i]) Div Osn; p>
C [i]: = (C [i] + A [i] + B [i]) Mod Osn p>
(Чи є в цих операторах помилка?) p>
End; p>
If
C [k + l] = 0 Then C [0]: = k Else C [0]: = k + l p>
End; p>
Четверта
завдання. Реалізація операцій порівняння для "довгих" чисел (А = В,
АВ, А = В). P>
Function Eq (A, B: TLong):
Boolean; p>
Var i:
Integer; p>
Begin p>
Eq: = False; p>
If A [0] B [0] Then Exit p>
Else Begin p>
i: = l; p>
While (i В також прозора. p>
Function More (A, B: Tlong):
Boolean; p>
Var i:
Integer; p>
Begin If A [0]
Else
If A [0]> B [0] Then More: =
True p>
Else Begin p>
i: =
A [0]; p>
While (i
> 0) And (A [i] = B [i]) Do Dec (i); p>
If i = 0
Then More: = False p>
Else
If A [i]> B [i] Then More: = True p>
Else
More: = False p>
End p>
End; p>
Решта
функції реалізуються через функції Eq і More. p>
Function Less (A, B: Tlong):
Boolean; (A
Begin p>
Less: = Not (More (A, B) Or Eq (A, B)) p>
End; p>
Function
More_Eq (A, B: Tlong): Boolean; (A> = B) p>
Begin p>
More_Eq: = More (A, B) Or Eq (A, B) p>
End; p>
Function
Less_Eq (A, B: Tlong): Boolean; (A
B [0] + sdvig Then More: = 0 p>
Else
p>
If A [0]
Else Begin p>
i: =
A [0]; p>
While
(i> sdvig) And p>
(A [i]
= B [i-sdvig]) Do Dec (i); p>
If i =
sdvig Then Begin p>
More: = 0; p>
(збіг
чисел з урахуванням зсуву) p>
For i
: = 1 To sdvig Do p>
If A [i]> 0 Then Exit; p>
More: = 2; p>
(числа
рівні, "хвіст" числа А дорівнює нулю) p>
End p>
Else
More: = Byte (A [i]
End p>
End; p>
П'ята задача.
Множення довгого числа на коротке. Під коротким розуміється ціле число типу
LongInt. P>
Процедура дуже
походить на процедуру складення двох довгих чисел. p>
Procedure Mul (Const A: TLong;
Const До:
Longlnt; Var З:
TLong); p>
Var i:
Integer; p>
(результат - значення змінної С) p>
Begin p>
FillChar (С, SizeOf (С), 0); p>
If K = 0 Then Inc (С [0]) (множення на нуль) p>
Else
Begin p>
For i: = l To A [0] Do p>
Begin p>
C [i + l]
: = (LongInt (A [i]) * K + C [i]) Div Osn; p>
C [i]
: = (LongInt (A [i]) * K + C [i]) Mod Osn p>
End; p>
If C [A [0] +1]> 0 Then C [0]: = A [0]
+ 1 p>
Else C [0]: = A [0] p>
(визначаємо довжину результату) p>
End p>
End; p>
Шоста задача.
Віднімання двох довгих чисел з урахуванням зсуву p>
Якщо поняття
зсуву поки не зрозуміло, то залиште його в спокої, насправді віднімання з
урахуванням зсуву буде потрібно при реалізації операції ділення. На початку з'ясуйте
логіку роботи процедури при нульовому зсуві. p>
Введемо
обмеження: число, з якого віднімають, більше числа, яке віднімається.
Працювати з "довгими" негативними числами ми не вміємо. P>
Процедура була
б схожа на процедури додавання та множення, якби не одне "але" --
запозичення одиниці зі старшого розряду замість перенесення одиниці в старший
розряд. Наприклад, у звичайній системі числення ми віднімаємо 9 з 11 - йде
запозичення 1 з розряду десятків, а якщо з 10000 віднімаємо 9 - процес
запозичення трохи складніше. p>
Procedure Sub (Var A: TLong; Const
B: TLong; Const sp: Integer); p>
Var i, j:
Integer; p>
(з А віднімаємо В з урахуванням зсуву sp, результат віднімання в А) p>
Begin p>
For i: = l To B [0] Do p>
Begin Dec (A [i + sp], B [i ]); p>
j: = i ;{*} p>
(реалізація
складного запозичення) p>
while
(A [j + sp] <0) and (j Ost p>
8 p>
9 p>
504 = 63 * ((8 + 9) Div 2) p>
C
Отже, результат
- Ціла частина приватного - рівний (Up + Down) Div 2, залишок від ділення - різниця
між значеннями Ost і С. Нижню кордон (Down) змінюємо, якщо результат (С)
менше залишку, верхню (Up), - якщо більше. p>
Ускладнимо
приклад. Нехай А одно 27856, а В - 354. Підставою системи числення є
не 10, а 10000. p>
Down p>
Up p>
С p>
Ost = 27856 p>
0 p>
10000 p>
1770000 p>
C> Ost p>
0 p>
5000 p>
885000 p>
C> Ost p>
0 p>
2500 p>
442500 p>
C> Ost p>
0 p>
1250 p>
221250 p>
C> Ost p>
0 p>
625 p>
110448 p>
C> Ost p>
0 p>
312 p>
55224 p>
C> Ost p>
0 p>
156 p>
27612 p>
C
78 p>
156 p>
41418 p>
C> Ost p>
78 p>
117 p>
34338 p>
C> Ost p>
78 p>
97 p>
30798 p>
C> Ost p>
78 p>
87 p>
29028 p>
C> Ost p>
78 p>
82 p>
28320 p>
C> Ost p>
78 p>
80 p>
27966 p>
C> Ost p>
78 p>
79 p>
27612 p>
C<
Ost p>
Ціла частина
приватного дорівнює 78, залишок від ділення - 27856 мінус 27612, тобто 244. P>
Пора приводити
процедуру. Використовувані "цеглинки": функція порівняння чисел (More) з
урахуванням зсуву і функція множення довгого числа на короткий (Mul) описані
вище. p>
Function FindBin (Var Ost: Tlong; Const В: TLong; Const sp: Integer):
Longint; p>
Var Down, Up: Word; C: TLong; p>
Begin p>
Down: = 0; Up
: = 0sn; p>
(основа системи числення) p>
While Up - l> Down Do p>
Begin p>
(Є
можливість викладачу зробити p>
свідому
помилку. Змінити умова p>
циклу
на Up> Down. Результат - зациклення програми.) P>
Mul (В, (Up + Down) Div 2, С); p>
Case More (Ost, C, sp) Of p>
0: Down: = (Down + Up) Div 2; p>
1: Up: = (Up + Down) Div 2; p>
2: Begin Up: = (Up + Down) Div 2;
Down: = Up End; p>
End; p>
End; p>
Mul (B, (Up +
Down) Div 2, C); p>
If More (Ost,
C, 0) = 0 Then Sub (Ost, C, sp) p>
(знаходимо залишок від ділення) p>
Else begin Sub (C, Ost, sp); Ost: =
C end; p>
FindBin: = (Up
+ Down) Div 2; p>
(ціла частина приватного) p>
End; p>
Залишилось
розібратися зі зрушенням, значенням змінної sp в нашому викладі. Знову
повернемося до звичайної системі числення і спробуємо розділити, наприклад, 635 на
15. Що ми робимо? Спочатку ділимо 63 на 15 і формуємо, підбираємо в розумі першим
цифру результату. Підбирати за допомогою комп'ютера ми навчилися. Підібрали - це
цифра 4, і це старша цифра результату. Змінимо залишок. Якщо спочатку він був
635, то зараз став 35. Віднімати з урахуванням зсуву ми вміємо. Знову підбираємо
цифру. Другу цифру результату. Це цифра 2 і залишок 5. Отже, результат (ціла
частина) 42, залишок від ділення 5. А що зміниться, якщо підставою буде не 10,
а 10000? Логіка співпадає, тільки в розумі вважати дещо важче, але ж у
нас же є молоток під назвою комп'ютер - нехай він забиває цвяхи. p>
Procedure MakeDel (Const А, В: TLong; Var Res, Ost: TLong); p>
Var sp: Integer; p>
Begin p>
Ost: = A; (первинне значення залишку) p>
sp: = А [0] - В [0]; p>
If More (А, В, sp) = l Then Dec (sp); p>
(B * Osn> A, в результаті одна цифра) p>
Res [0]: = sp + l; p>
While sp> =
0 Do p>
Begin p>
(знаходимо чергову цифру результату) p>
Res [sp
+ 1]: = FindBin (Ost, B, sp); p>
Dec (sp) p>
End p>
End; p>
Методичні
рекомендації. Представлений матеріал викладається на чотирьох заняттях з
відомою схемою: 10-15-хвилинне виклад ідей, а потім робота учнів під
керівництвом викладача. p>
1-е заняття.
Введення, висновок і складання довгих чисел (завдання 1, 2, 3). P>
2-е заняття.
Функції порівняння (задача 4). P>
3-е заняття.
Множення і віднімання довгих чисел (завдання 5, 6). P>
4-е заняття.
Розподіл довгих чисел (завдання 7). Безумовно, ця схема не догма. Залежно
від рівня підготовки учнів на самостійне виконання може бути винесена
значна частина матеріалу. Зазначу тільки, що в силу традицією, що склалася в
ряді випадків допускаються при викладі свідомі помилки. У результаті роботи
кожен учень повинен мати власний модуль для роботи з
"довгими" числами. p>
Теми для
досліджень p>
1. Рішення
задач: пошук найбільшого загального дільника двох "довгих" чисел; пошук
найменшого спільного кратного двох "довгих" чисел; витяг
квадратного кореня з "довгого" числа і т.д. p>
2.
"Довгі" числа можуть бути негативними. Як зміняться описані
вище операції для цього випадку? p>
3. Для зберігання
"довгих" чисел використовується не масив, а стек, реалізований з
допомогою списку. Змінити модуль роботи з "довгими" числами. P>
Список
літератури h2>
С.М. Окулов /
"Довга" арифметика/ p>
Для підготовки
даної роботи були використані матеріали з сайту http://www.comp-science.narod.ru/
p>