Проблемы целочисленной машинной арифметики

Несмотря на достоинства в двоичной машинной (аппаратной) арифметике имеются очень неприятные особенности, возникающие из-за конечной разрядности машинной ячейки.

Проблемы сложения положительных чисел

Пусть a=310=00112; b=210=00102; a+b=01012=510, то есть все в порядке.

Пусть теперь a=610=01102, b=510=01012. Тогда a+b =10112= -32.

То есть сложение двух положительных чисел может дать отрицательное, если результат сложения превышает максимальное положительное число, выделяемое под целое со знаком для данной разрядности ячеек! В любом случае при выходе за разрешённый диапазон значений результат оказывается неверным.

Если у нас беззнаковые целые, проблема остается в несколько измененном виде. Сложим 810+810 в двоичном представлении. Поскольку 810=10002, тогда 810+810= 10002+ 10002=100002. Но лишний бит отбрасывается, и получаем 0. Аналогично в четырёхбитной арифметике, 810+ 910=110, и т.д.

Как уже говорилось, умножение двоичных чисел осуществляется путем сложений и сдвигов по алгоритму, напоминающему умножение “в столбик”, но гораздо более простому, так как умножить надо только на 0 или 1. При целочисленном умножении выход за пределы разрядности ячейки происходит гораздо чаще, чем при сложении или вычитании. Например, 1102 1012=1102 1002+1102 12=110002+1102=111002. Если наша ячейка четырехразрядная, произойдет выход за ее пределы, и мы получим после отбрасывания лишнего бита 11102= -210<0. Таким образом, умножение целых чисел легко может дать неправильный результат. В том числе – даже отрицательное число. Поэтому при работе с целочисленными типами данных следует обращать особое внимание на то, чтобы в программе не возникало ситуаций арифметического переполнения. Повышение разрядности целочисленных переменных позволяет смягчить проблему, хотя полностью её не устраняет. Например, зададим переменные

byte m=10,n=10,k=10;

Тогда значения m*n, m*k и n*k будут лежать в разрешённом диапазоне

-128..127. А вот m*n + m*k из него выйдет. Не говоря уж об m*n*k.

Если мы зададим

int m=10,n=10,k=10;

переполнения не возникнет даже для m*n*k. Однако, при m=n=k=100 значение m*n*k будет равно 106, что заметно выходит за пределы разрешённого диапазона –32768..32767. Хотя m*n, m*k и n*k не будут за него выходить (но уже 4*m*n за него выйдет). Использование типа long поможет и в этом случае. Однако уже значения m=n=k=2000 (не такие уж большие!) опять приведут к выходу m*n*k за пределы диапазона. Хотя для m*n выход произойдёт только при значениях около 50000.

Вычисление факториала с помощью целочисленной арифметики даст удивительные результаты! В таких случаях лучше использовать числа с плавающей точкой.

Пример:

byte i=127, j=1, k;

k=(byte)(i+j);

System.out.println(k);

В результате получим число (-128). Если бы мы попробовали написать

byte i=127,j=1,k;

System.out.println(i+j);

то получили бы +128. Напомним, что значения величин типа byte перед проведением сложения преобразуются в значения типа int.