Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Целочисленная арифметика.doc
Скачиваний:
8
Добавлен:
09.09.2019
Размер:
313.86 Кб
Скачать

1. Взаимное преобразование обычных и "длинных" чисел.

Часто бывает нужно выполнять "длинные" вычисления, в которых исходные данные представлены обычными целыми числами. Для этого нужно сначала преобразовать обычное число в "длинное".

Прототип соответствующей функции хотелось бы записать так:

large item21arge (item) ;

К сожалению, в классическом Си это невозможно. Во-первых, массив не может быть результатом функции. Во-вторых, возникают проблемы с выделением памяти для хранения результата.

Поэтому вместо настоящей функции придется создавать функцию с фиктивным результатом, а подлинный результат передавать в качестве второго параметра, используя при этом тот факт, что указатели в списке параметров в Си эквивалентны массивам. Получаем такой прототип:

void item21arge (item, large) ;

Обратное преобразование можно представить обычной функцией. Ее прототип:

item large2item (large);

Упражнение

Напишите функции по заданным прототипам.

Подсказка. Эта задача очень похожа на преобразование числа в строку и обратно,

2. Сложение и вычитание "длинных" чисел.

Сложение двух "длинных" чисел очень похоже на обычное школьное сложение столбиком. Числа складываются поразрядно. Если сумма в каком-то разряде окажется больше основания системы счисления, то в соответствующий разряд результата записывается остаток от деления суммы на это основание, а в следующий разряд переносится единица.

Как и в случае с функцией item21arge, мы вынуждены программировать функцию с фиктивным результатом, а настоящий результат передавать в качестве дополнительного параметра.

void largeadd (large a, large b/ large sum)

/* Сложение "длинных" чисел, sum == a+b */

{int i.; /* счетчик разрядов */

item carry=0;

/* перенос в следующий разряд */

for (i=0; i<N; i++)

{ sum[i]=a[i]+b[i]+carry;

carry=sum[i]/B;

sum[i] %==B; }

}

Контрольные вопросы

1. Что изменилось бы в этой функции, если бы мы приняли другое решение о представлении "длинных" чисел?

2. Что произойдет, если сумма а [ i ] +b [ i ] окажется больше максимально допустимого значения типа item? Как можно избежать этой ситуации?

3. Что произойдет, если сумма окажется больше максимально допустимого значения для "длинного" числа? Можно ли избежать этой ситуации?

4. Будет ли функция корректно работать при вызове largeadd (х, у, х) ? А при вызове largeadd (х, х, х) ?

Упражнение

Разработайте функцию, выполняющую вычитание "длинных" чисел.

3. Сравнение "длинных" чисел.

Приведем соответствующую функцию без дополнительных пояснений.

int largecmp (large a, large b)

/* Сравнение "длинных" чисел. Результат функции отрицателен, если а < b, положителен, если а > b, равен нулю, если а = b */

{int i;

for (i=N-l; i>0 && a[i]==b[i]; i--) ;

return a[i]-b[i];

}

Контрольные вопросы и упражнения

1. Почему перебор производится с конца массива?

2. Почему использовано сравнение i>0,a не i>=0 ?

3. Напишите функцию largeicmp, сравнивающую "длинное" число с обычным.

4. Умножение "длинного" целого на обычное целое.

Эта функция очень похожа на сложение — точно так же моделируется вычисление столбиком, точно так же на каждом шаге определяется перенос в .следующий разряд.

void largeimul (large a, int b)

/* Умножение "длинного" целого на обычное.а*= b */

{int i;

/* счетчик разрядов */

item carry=0;

/* перенос в следующий разряд */

for (i=0; i<N; i++)

{ a [i] ==a [i] *b+carry;

carry=a[i]/B;

a[i]%=B;

}

}

5. Деление "длинного" целого на обычное целое.

И снова моделируется обычный столбик. Эта операция оказывается несколько сложнее умножения и сложения, но принцип остается тем же.

item largeidiv (large a, item d)

/* Деление "длинного" целого на обычное с остатком. a/==d. Результат функции — остаток от деления */

{ int i;item carry=0;

for (i==N-l; i>=0; i--)

{ a[i]+=B*carry;

carry = a[i]%d;

a[i]/=d; }

return carry;

}

Контрольный вопрос

В правильном "длинном" числе все элементы массива должны быть меньше основания системы счисления В. Докажите, что функция деления сохраняет это свойство.

6. Умножение "длинных" целых.

Умножение "длинных" чисел можно реализовать все по той же схеме "столбика". Попробуйте самостоятельно запрограммировать такую функцию.

Но есть и другой способ умножения, который во многих случаях оказывается более эффективным. Это так называемое "быстрое умножение", позволяющее заменить умножение натуральных чисел небольшим числом сложений.

Метод быстрого умножения основан на следующих соотношениях: Если b — четное число, то

а*b= (2а)*(b/2) . (1)

Если b — нечетное число, то

а*b== а+ а* (b-1) (2)

Чтобы лучше понять работу метода, применим его сначала для умножения обычных, не "длинных" чисел, считая, что обычная операция умножения по каким-то причинам недоступна.

unsigned long mult (unsigned long a,

unsigned long b)

/* Быстрое умножение a*b с помощью небольшого числа сложений */

{ unsigned long s=0;

/* собственно произведение */

while (b>0)

/* Инвариант: искомое произведение р = s + a*b */

if (b%2) { a+=a; b/=2; }

else { s+=a; b--; }

return s;

}

На первый взгляд эта функция выполняет какие-то странные действия, трудно поверить, что результатом будет произведение чисел а и b. Однако несложный анализ позволяет убедиться, что это действительно так.

Обратите внимание на соотношение р = s + а • b. Это, — инвариант цикла, то есть условие, которое выполнено при каждом исполнении цикла. При первом исполнении инвариант, очевидно, верен: в этот момент s = 0, а и b сохраняют свои первоначальные значения. Последующие преобразования выполняются в соответствии с ранее записанными правилами (1) и (2), которые сохраняют справедливость инварианта. Когда цикл завершится (подумайте, почему это обязательно произойдет?), будет выполнено условие b = 0. В сочетании с инвариантом это дает р = s , следовательно, переменная s действительно будет содержать искомое произведение.

Теперь можно переписать функцию быстрого умножения для "длинных" чисел. При этом мы используем тот очевидный факт, что после вычитания единицы из нечетного числа получается четное, а также то, что ранее написанная функция largeidiv одновременно вычисляет частное и остаток.

void largemult (large a, large b/ large s)

/* Быстрое умножение "длинных" чисел

s == а * b

!! Внимание !!

Исходные данные (а и b) изменяются в процессе вычислений.

Если нужно сохранить исходные

значения, перед умножением следует сделать копию. */

{ item2large (0,s);

while (largeicmp(b,0))

/* Инвариант: искомое произведение р = s + a*b */

if (largeidiv(b,2)) largeadd(s,a,s);

largeadd(a,a,a);

}

Контрольные вопросы и упражнения

1. Почему функция largemuIt "портит" значения своих аргументов, а в функции mult этого не происходит?

2. Перепишите функцию largemuIt так, чтобы аргументы не изменялись.

7. Ввод и вывод "длинных" целых.

До сих пор мы рассматривали "внутренние" операции с "длинными" числами. Их список получился неполным, но общий принцип ясен, и при необходимости набор функций можно; легко дополнить.

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

Поскольку ввод и вывод удобно делать в привычной нам десятичной системе, речь фактически идет о преобразовании между системой с основанием В и десятичной.

Очевидно, что это преобразование существенно упрощается, если В — точная степень десяти. Напишем, например, функцию вывода "длинного" числа при В = 10.

void largeprinti (large a)

/* Вывод "длинного" числа, записанного по основанию В=10 */

{ int i;

/* пропускаем незначащие ведущие нули */

for (i=0; i.<N-1 && a[i]=0; i++) ;

/* выводим значащие цифры */

for (; i<N; i++) printf("%d",a[i]);

}

Контрольный вопрос

Что произойдет, если "длинное" число окажется равным нулю?

Итак, если записывать "длинные" числа в десятичной системе, их вывод программируется очень просто. Но 10 — очень маленькое основание. При В = 10 для хранения больших чисел придется брать очень большое N, что значительно увеличит время работы всех функций.

Если взять В =10k, в каждом элементе "длинного" числа поместится k десятичных цифр. Это позволит в k раз уменьшить значение N для представления того же диапазона "длинных" чисел, что существенно ускорит время вычисление.

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

С учетом этих ограничений получаем, что при 32-разрядных элементах "длинного" числа наиболее удобное значение В =105=10 000, то есть в каждом элементе "длинного" числа хранится по 4 десятичные цифры.

Казалось бы, для вывода "длинных" чисел с основанием 10 000 можно использовать уже написанную функцию largeprint1. Но для некоторых чисел результат будет получаться неправильным. (Прежде чем читать дальше, подумайте, в каких случаях это будет происходить.)

Дело в том, что некоторые "цифры" в 10 000-ичной системе счисления окажутся меньше 1000. При выводе они будут напечатаны без ведущих нулей, что существенно исказит подлинное значение "длинного" числа. Поэтому функцию вывода придется немного подправить.

void largeprint5 (large a)

/* Вывод "длинного" числа, записанного по основанию В=10000 */

{ int i;

/* пропускаем незначащие ведущие нули */

for <i==0; i<N-l && a[i]==0; i++) ;

/* старшую цифру выводим без ведущих нулей */

printf("%d",a[i]);

/* выводим остальные цифры с ведущими нулями*/

for (i++; i<N; i++) printf("%04d",a[i]); }

Упражнение

Напишите функцию для ввода "длинного" числа.

Задачи для самостоятельного решения

1. Напишите функцию для возведения натурального числа в натуральную степень. Основание и показатель степени не превосходят 10 000. Постарайтесь обойтись как можно меньшим числом умножений.

(Подсказка. Алгоритм быстрого возведения в степень можно получить из алгоритма быстрого умножения, заменив сложение умножением.) ;

2. "Длинное" число записано в массив, по 4 десятичные цифры в каждом элементе (В = 10 000). Требуется сформировать "длинное" число, которое получится, если записать исходное десятичное число задом наперед.

3. Разработайте функции ввода и вывода "длинных" чисел для случая, когда параметр В не является степенью числа 10.