eolymp

Арифметика великих чисел

Відомо, що операції з числовими значеннями в комп’ютері відбувається з обмеженою кількістю розрядів. Але трапляються випадки, коли приходиться обчислювати числа де кількість знаків становить 20, 100, 1000. Такі числа використовуються в астрономії, мікрофізиці, комбінаториці.

Для реалізації операцій з довгими числами необхідно розмістити довге число в пам’яті комп’ютера. Розмістимо довге число в цілочисельний масив. В кожну комірку пам’яті помістимо по декілька цифр (для початку 1 цифру) В нульовій комірці пам’яті будемо зберігати кількість цифр довгого числа. Розвернемо масив для зручності уявлення розміщення цифр.

Запишемо число: 475739541

Масив M:

Номер елементів масиву

9

8

7

6

5

4

3

2

1

0

Елементи масиву

4

7

5

7

3

9

5

4

1

9

 

 Для реалізації довгої арифметики створимо такі функції:

  • занесення довгого числа в масив;
  • виведення довгого числа;
  • додавання двох довгих чисел;
  • порівняння довгих чисел;
  • множення довгого числа на коротке;
  • множення двох довгих чисел;
  • ділення довгого числа на коротке;
  • ділення довгого на довге число.

 

Довге число зручно вводити у вигляді рядка, при цьому у нульовій комірці розміщується цифра самого високого розряду, а останньою розміщена цифра першого розряду. Розвернемо (реверсуємо) рядок і запишемо кожну цифру в окрему комірку  цілочисельного масиву. Для цього напишемо функцію:

// Переведення з рядка в масив int
void strtomasiv(char *str, int *T)
{
	int i;

	T[0] = strlen(str); // Знаходження кількості знаків у числі
for (i = 0; i < T[0]; i++)
				T[T[0] - i] = str[ i] -  '0';  // переведення з символів у цифри і занесення їх у числовий масив
// одночасно розвертаємо масив
}

 

Функція strtomasiv отримує довге число у вигляді символьного масиву *str, і повертає також через параметри масив *T типу int.

Для прискорення операцій з довгими цифрами у кожну комірку заносять не одну цифру, а декілька. Число типу int (long) у мові С++ може пам’ятати 10 знаків. Враховуючи діапазон данного типу (перша цифра максимального числа не перевищує 2) ми можемо записати у кожну комірку пам’яті массиву по 9 цифр при додаванні  та 4 цифри при множенні.

// Переведення з рядка в масив int// із занесенням по K знаків у кожну коміркуvoid strtomasiv(char *str, int *T)
{
  int i, j , p, n, K;
  char temp[10];
  
  K = 2;
  T[0] = (strlen(str) / K) + 1; // кількість "розрядів" довгого числаfor (i = 1; i < T[0] + 1; i++)
  {
    strncpy(temp, str + ((i - 1) * K), K); //копіювання "розряду" довгого числа
  	
    p = 0;
    n = strlen(temp); // розмір "розряду" - може бути меншим за Kfor (j = 0; j < n; j++)
      p = (p * 10) + (temp[j] - '0'); // перетворення рядкового значення "розряду" до числа

    T[i] = p;
  }
}

Для виведення довгих чисел також рекомендується написати окрему функцію.

Під час виведення масиву довгого числа, де в кожній комірці знаходиться лише по одній цифрі не виникає ніяких проблем, необхідно просто вивести масив у зворотному порядку.

// Виведення довгого числаvoid out1(int *T)
{
  int i, j, p;

  // перебираємо всі розряди числа від найстаршогоfor (i = T[0]; i > 0; i--)
  {
  	cout << T[i];
  }
  
  cout << endl;
}

Якщо виводите масив, де в кожній комірці знаходиться по декілька цифр - можливі проблеми з виведенням ведучих нулів. Якщо нам потрібно вивести, наприклад, число 12030002, і в кожній комірці знаходтться по 4 цифри, то ми отримуємо масив з двох комірок, T[1]=2, та T[2]=1203. Під час виведення за допомогою функції, наведеної вище, ми отримаємо 12032. Ми втрачаємо три розряди, значення яких дорівнюють 0. В такому випадку необхідно застосувати форматований вивід. Для цього скористаємось двома флагами форматованого виведення:

void out2(int *T)
{
  int i, p;

  for (i = T[0]; i > 0; i--)
  {
    cout << T[i];
  	
    cout.width(K); //задається ширина поля виведення К знаків з вирівнюванням по правому краю
    cout.fill('0'); //заповнення пустих позицый символом '0'.
  }
  
  cout << endl;
}
void out2(int *T)
{
  int i, p;
  for (i = T[0]; i > 0; i--)
  {
 cout << T[i];
 
    cout.width(K); //задається ширина поля виведення К знаків з вирівнюванням по правому краю
    cout.fill('0'); //заповнення пустих позицый символом '0'.
  }
  
  cout << endl;
}

Додавання цілих довгих чисел. Процес додавання довгих чисел – це реалізація додавання чисел "в стовпчик". Перше число – масив T1, друге число – масив T2, результат – T3.

Наприклад:

longhand_add

void add(int *T1, int *T2, int* T3) //додавання довгих чисел T3=T1+T2;
{
    int n = (T1[0] > T2[0]) ? T1[0] : T2[0]; // знаходиться кількість цифр більшого  числаint p = 0, i, R = pow(10, K);

    for (i = 1; i <= n; i++)
    {
        int temp = T1[i] + T2[i] + p;
		T3[i] = temp % R; // виділення результуючої групи цифр
        p = temp / R; // виділення цифри, що виходить за розрядність групи
    }
    
    if (p > 0) 
    {
    	n++; 
    	T3[n] = p; 
    }
    
    T3[0] = n;
}

 

Порівняння довгих чисел. 

Функція порівняння довгих чисел повертає 0 – коли числа рівні, - коли перше число більше за друге і -1 - коли друге число більше першого. Під час реалізації спершу порівнюється кількість цифр. Якщо кількість цифр різна, то у більшого числа більше кількість цифр. Якщо ж кількість цифр  однакова, то порівнюють всі цифри по порядку, починаючи зі старшого розряду. Даний алгоритм буде працювати правильно і у випадку коли в одній комірці пам’яті масиву будемо зберігати декілька цифр.

// Порівняння довгих чисел: 0 - рівні, 1 - (T1 > T2), -1 - (T1 < T2)int compare(int *T1,int  *T2)
{
    int i;

    if (T1[0] > T2[0]) // якщо в T1 цифр більше, ніж в T2return 1;
    elseif (T1[0] < T2[0]) // якщо в Т2 цифр більше, ніж в Т1return -1;
    else
    {
        for (i = 1; i < T1[0]; i++)
        {
            // якщо чергові цифри співпадають - перевіряємо даліif (T1[i] == T2[i])
                continue;
            elseif (T1[i] > T2[i])
                return 1;
            elsereturn -1;
        }

        // якщо перевірили всі цифри, і всі рівні - числа рівніreturn 0;
    }
}

 

Множення довгого числа на коротке

Виконується множення "в стовпчик" на одноцифрове число. Дана операція може бути корисна для виконання простих операцій, таких як множення на 2 або використана при діленні довгого числа на довге. Різниця між множенням на коротке число і множенням на довге полягає в тому, що при множенні на коротке число нам не потрібно множити розряди довгого числа на відповідні розряди короткого числа.

// Множення довгого числа на коротке: T1 = T1*k
void mult(int *T1, int k)
{
	int i, p = 0, R = pow(10, K);

	for (i = 1; i <= T1[0]; i++)
	{
		int temp = (T1[i] * k) + p;
		T1[i] = temp % R;
		p = temp / R;
	}

	while (p > 0)
	{
		T1[0]++;
		T1[T1[0]] = p % R;
		p /= R;
	}
}

 

Множення двох довгих чисел

// Множення довгого числа на довге: T3 = T1 * T2
void multDD(int *T1, int *T2, int *T3)
{
    int i, j, p = 0, R = pow(10, K);

    for (i = 1; i <= T2[0]; i++)
    {
        for (j = 1; j <= T1[0]; j++)
        {
            int temp = (T2[i] * T1[j]) + p + T3[j + i - 1];

            T3[i + j - 1] = temp % R;

            p = temp / R;
        }

        T3[i + j - 1] = p;
        
        p = 0;
    }

    int n = T2[0] + T1[0] - 1;

    while (p > 0)
    {
        T3[n] = p % R;
        p /= R;
        n++;
    }

    if (T3[n + 1] > 0)
        T3[0] = n + 1;
    else
        T3[0] = n;
}