Все залежить від достатнього зберігання та алгоритмів, щоб розглядати числа як менші частини. Припустимо, у вас є компілятор, в якомуint
може бути лише від 0 до 99, і ви хочете обробляти числа до 999999 (тут ми піклуємося лише про позитивні числа, щоб спростити це).
Ви робите це, даючи кожному номеру три int
s і використовуючи ті самі правила, які ви (повинні були) вивчити ще в початковій школі для додавання, віднімання та інших основних операцій.
У довільній бібліотеці точності немає фіксованого обмеження на кількість базових типів, що використовуються для представлення наших чисел, незалежно від того, яку пам’ять може вмістити.
Додаток, наприклад 123456 + 78
::
12 34 56
78
-- -- --
12 35 34
Працюючи з найменш значущого кінця:
- початковий перенос = 0.
- 56 + 78 + 0 перенесення = 134 = 34 з 1 перенесенням
- 34 + 00 + 1 перенесення = 35 = 35 з 0 перенесенням
- 12 + 00 + 0 перенесення = 12 = 12 з 0 перенесення
Фактично це те, як додавання, як правило, працює на рівні бітів у вашому процесорі.
Віднімання аналогічне (з використанням віднімання базового типу та запозичення замість перенесення), множення може здійснюватися повторними додаваннями (дуже повільно) або перехресними добутками (швидше), а ділення складніше, але може бути здійснено шляхом зсуву та віднімання чисел залучений (довгий поділ, про який ви дізналися б у дитинстві).
Я насправді писав бібліотеки, щоб робити подібні речі, використовуючи максимальну потужність десяти, яку можна вписати в ціле число, коли в квадраті (щоб запобігти переповненню при множенні двох int
s разом, наприклад, 16-біт int
обмежується від 0 до 99 до генерувати 9 801 (<32 768) у квадраті або 32-бітint
використовуючи від 0 до 9,999, щоб генерувати 99,980,001 (<2,147,483,648)), що значно полегшило алгоритми.
Деякі хитрощі, на які слід стежити.
1 / Під час додавання чи множення чисел заздалегідь виділіть максимум необхідного місця, а потім зменште, якщо виявите, що його занадто багато. Наприклад, додавання двох 100- "цифр" (де цифра - це int
) ніколи не дасть вам більше 101 цифри. Помноження 12-значного числа на 3-значне число ніколи не дасть більше 15 цифр (додайте кількість цифр).
2 / Для додаткової швидкості нормалізуйте (зменшіть обсяг пам’яті, необхідний для) номери лише за крайньої необхідності - у моїй бібліотеці це було окремим викликом, щоб користувач міг вирішувати питання швидкості та сховища.
3 / Додавання додатного і від’ємного числа - це віднімання, а віднімання від’ємного числа - те саме, що додавання еквівалентного додатного числа. Ви можете заощадити чимало коду, якщо методи додавання та віднімання викликають один одного після налаштування знаків.
4 / Уникайте віднімання великих чисел від малих, оскільки незмінно ви отримуєте такі числа, як:
10
11-
-- -- -- --
99 99 99 99 (and you still have a borrow).
Натомість відніміть 10 з 11, а потім заперечіть:
11
10-
--
1 (then negate to get -1).
Ось коментарі (перетворені в текст) з однієї з бібліотек, для яких мені довелося це зробити. Сам код, на жаль, захищений авторським правом, але, можливо, ви зможете вибрати достатньо інформації для обробки чотирьох основних операцій. Припустимо в наступному, що -a
і -b
представляють від’ємні числа і a
таb
є нульовими або додатними числами.
Для того , якщо ознаки різні, використання вирахування заперечення:
-a + b becomes b - a
a + -b becomes a - b
Для віднімання , якщо ознаки різні, використовуйте додавання заперечення:
a - -b becomes a + b
-a - b becomes -(a + b)
Також спеціальна обробка, яка забезпечує віднімання малих чисел від великих:
small - big becomes -(big - small)
Множення використовує математику початкового рівня наступним чином:
475(a) x 32(b) = 475 x (30 + 2)
= 475 x 30 + 475 x 2
= 4750 x 3 + 475 x 2
= 4750 + 4750 + 4750 + 475 + 475
Спосіб, яким це досягається, включає вилучення кожної з 32 цифр по черзі (назад), а потім використання додавання для обчислення значення, яке потрібно додати до результату (спочатку нуль).
ShiftLeft
і ShiftRight
операції використовуються для швидкого множення або ділення а LongInt
на значення обтікання (10 для "реальної" математики). У наведеному вище прикладі ми додаємо 475 до нуля 2 рази (остання цифра 32), щоб отримати 950 (результат = 0 + 950 = 950).
Потім ми залишили зсув 475, щоб отримати 4750, і правий зсув 32, щоб отримати 3. Додайте 4750 до нуля 3 рази, щоб отримати 14250, а потім додайте до результату 950, щоб отримати 15200.
Лівий зсув 4750 отримує 47500, правий зсув 3 - 0. Оскільки зсунутий вправо 32 тепер дорівнює нулю, ми закінчили, і насправді 475 x 32 дорівнює 15200.
Ділення також хитре, але базується на ранній арифметиці (метод "gazinta" для "переходить у"). Розглянемо наступний довгий поділ для 12345 / 27
:
457
+-------
27 | 12345 27 is larger than 1 or 12 so we first use 123.
108 27 goes into 123 4 times, 4 x 27 = 108, 123 - 108 = 15.
---
154 Bring down 4.
135 27 goes into 154 5 times, 5 x 27 = 135, 154 - 135 = 19.
---
195 Bring down 5.
189 27 goes into 195 7 times, 7 x 27 = 189, 195 - 189 = 6.
---
6 Nothing more to bring down, so stop.
Тому 12345 / 27
це 457
з залишком 6
. Перевірити:
457 x 27 + 6
= 12339 + 6
= 12345
Це реалізується за допомогою зменшувальної змінної (спочатку нульової), щоб збити сегменти 12345 по одному, поки вона не буде більшою або дорівнює 27.
Потім ми просто віднімаємо від цього 27, поки не опустимося нижче 27 - кількість віднімань - це сегмент, доданий у верхній рядок.
Коли немає більше сегментів, які можна збити, ми маємо свій результат.
Майте на увазі, це досить базові алгоритми. Існують набагато кращі способи складання арифметики, якщо ваші числа будуть особливо великими. Ви можете переглянути щось на зразок арифметичної бібліотеки GNU Multiple Precision Arithmetic - це набагато краще та швидше, ніж мої власні бібліотеки.
У нього є досить прикрі помилки в тому, що він просто вийде, якщо в нього закінчиться пам’ять (на мій погляд, досить фатальний недолік бібліотеки загального призначення), але, якщо ви зможете пройти мимо цього, це досить добре в тому, що він робить.
Якщо ви не можете використовувати його з ліцензійних причин (або тому, що не хочете, щоб програма просто виходила без видимих причин), ви можете принаймні отримати алгоритми звідти для інтеграції у власний код.
Я також виявив, що боди в MPIR (форк GMP) є більш придатними для обговорення потенційних змін - вони здаються більш зручним для розробників.