Набор Алгоритмов

Статья содержит побитовое представление базовых математических операций и класс для представления неограниченных целых чисел максимальным и минимальным значением.

Алгоритмы - Побитовые операции. Побитовое описание математических операций над целыми числами

1. Введение

Процессор компьютера позволяет делить числа, ограниченнные в битах. В настоящей статье рассматривается, как возможно реализовать примитивные математические операции на основе побитовых операций. В статье рассматриваются, как и алгоритмы (примеры, на основе побитовых операций), так и конкретная реализация на массиве байтов.

2. Побитовое описание простейших математических операций (+,-,*,/,\)

Все описанные далее операции есть операции над целыми числами. В них возможно переполнение разрядной сетки. Все они реализованы на побитовых операциях или используя описанные в статье методы (зависимые от других методов операции).

2.1. Сложение

Cпособ сложить побитовыми операциями два целых числа:

// Сложение
public static long Add(long summand, long addend)
{
    // Перенос.
    long carry = 0x00;

    // Итерировать до тех пор, пока не закончится перенос на старший разряд.
    while (addend != 0x00)
    {
        // Выставить флаг под разрядами с установленными битами.
        carry = (summand & addend);

        // Снять с первого слагаемого биты, разряд по которым уже учтен.
        summand = summand ^ addend;

        // Перенос переводится в старший разряд.
        addend = (carry << 1);
    }

    return summand;
}

Код 1. Cпособ сложить побитовыми операциями два целых числа.

Код 1 заимствован из статьи на Хабре (https://habr.com/ru/post/512474/) и адаптирован для статьи.

Для начала вспомним сложение столбиком из школьной математики, в десятичной системе исчисления:

 +1
 234
+  9
 ___
 243

Числа складываются в столбик поразрядно (по порядкам). Значение суммы числел более 9 (от 10 до 18 (возможное складывание чисел от 0 до 9)) переносится на следующий рязряд (порядок). Порядок числа это номер цифры с конца начинач с нуля. Порядок чисел от 0 до 9 равен 0, порядок чисел от 10 до 99 равен 1 и т.д.. Порядок n — положительного числа a; это степень числа 10, при том что это число a находится находится в диапазоне от 10a до 10a+1, исключение, что для нулевого порядка нулевой порядок находится от 0 до 9.

Для двоичной системы исчисления, переносится аналогично, всё что больше её максимльно цифры, т.е. 1. Приведём примеры:

1+1=2:

 1
+1
__
10

3+3=6:

 11
+11
___
110

15+3=18:

 1111
+  11
_____
10010

Рассмотрим прохождение по алгоритму на примере сложение двух чисел (3+3=6).

Рассмотрим первую итерацию цикла.

В алгоритме для определения переноса служит переменная carry (перенос):

carry = (summand & addend);
0011 = 0011 & 0011

Это биты, где должен быть осуществлён перенос. Эти биты в слагаемом дожны быть освобождены:

summand = summand ^ addend;
0000 = 0011 ^ 0011

Это делается с помощью Исключающего ИЛИ. Которое выставляет 1, когда биты различные (противоположны).

Далее идёт преобразование «флагов» наличия переноса непосредственно в перенос. Для этого они сдвигаются на один шаг влево и присваиваются второму слагаемому:

addend = (carry << 1);
0110 = (0011 << 1);

Далее происходит второе повторение действий цикла.

Переноса на втором шаге нет:

carry = (summand & addend);
0000 = 0000 & 0110

Второй шаг выглядет так:

summand = summand ^ addend;
0110 = 0000 ^ 0110,

Итак, мы получили сумму. Цикл завершается следующим шагом:

addend = (carry << 1);
0000 = (0000 << 1)

Пример прохождения по алгоритму более сложных слагаемых:

Анимация 1. Пример прохождения по алгоритму сложения более сложных слагаемых.

2.2. Вычитание

Cпособ вычесть побитовыми операциями два целых числа:

// Вычитание
public static long Sub(long minuend, long subtrahend)
{
    // Отрицательный перенос.
    long borrow = 0x00;

    // Итерировать до тех пор, пока не закончится перенос на младший разряд.
    while (subtrahend != 0x00)
    {
        // Выставить флаг под разрядами с установленными битами.
        borrow = ((~minuend) & subtrahend);

        // Снять с уменьшаемого биты, разряд по которым уже учтен.
        minuend = minuend ^ subtrahend;

        // Перенос переводится в старший разряд.
        subtrahend = (borrow << 1);
    }

    return minuend;
}

Код 2. Cпособ вычесть побитовыми операциями два целых числа.

Код 2 заимствован из статьи на Хабре (https://habr.com/ru/post/512474/) и адаптирован для статьи.

Аналочен алгоритму сложения с разницей с тем, что заём битов у старших разрядов реализован инверсией обратной импликации (бинарной логической связки, по своему применению приближенная к союзам «если…, то…»...).

Рассмотрим алгоритм вычитаний, его цикл для вычислений 4-3=1 (100-11=1).

Итерация 1:

borrow = ((~minuend) & subtrahend);
11 = ((~100) & 11) = (11111011 & 11)

minuend = minuend ^ subtrahend;
111 = 100 ^ 11

subtrahend = (borrow << 1);
110 = (11 << 1)

Итерация 2:

borrow = ((~minuend) & subtrahend);
0 = ((~111) & 110) = (11111000 & 110)

minuend = minuend ^ subtrahend;
1 = 111 ^ 110

subtrahend = (borrow << 1);
0 = (0 << 1)

Пример прохождения по алгоритму более сложных параметров:

Анимация 2. Пример прохождения по алгоритму вычитания более сложных параметров.

2.3. Умножение

Cпособ умножить побитовыми операциями два целых числа:

// Умножение
public static long Multiply(long mul1, long mul2)
{
    // Результат.
    long result = 0;

    // Пока второй множитель не равен нулю.
    while (mul2 != 0)
    {
        // Если установлен бит в очередном разряде...
        if ((mul2 & 1) == 1)
        {
            // сложить первый множитель с самим собою.
            result = Add(result, mul1);
        }

        // Очередной сдвиг первого множителя влево.
        mul1 <<= 1;

        // Подводим очередной разряд в начало для сверки с условием оператора if().
        mul2 >>= 1;
    }

    return result;
}

Код 3. Cпособ умножить побитовыми операциями два целых числа.

Код 3 заимствован из статьи на Хабре (https://habr.com/ru/post/512474/) и адаптирован для статьи.

Вспомним умножение в столбик из школьной программы:

  234
х  23
_____
  702
+468
_____
 5382

Аналогично производится умножение в столбик, но для двоичной системы исчисления:

    0111
    0011
    ____
+   0111
+  0111
+ 0000
+0000
________
   10101

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

if ((mul2 & 1) == 1)
{
    // сложить первый множитель с самим собою.
    result = Add(result, mul1);
}

Далее осуществляется сдвиг первого множителя на один порядок влево, как и описывалось ранее:

mul1 <<= 1;

Чтобы определить текщий значащий бит, сдвигаем второй множитель в право, нам от него нужен только первый бит:

mul2 >>= 1;

Пример прохождения по алгоритму более сложных параметров:

Анимация 3. Пример прохождения по алгоритму умножения более сложных параметров.

2.4. Деление

Cпособ разделить побитовыми операциями два целых числа:

// Хотя и входные данные long, их значения должны быть не больше int
public static long Divide(long dividend, long divisor)
{
    // Инициализация результата
    long ans = 0;

    // Получаем знак результата
    bool isNegative = (dividend < 0) ^ (divisor < 0);

    // Получаем модуль числа (абсолютное значение) для входных параметров (делимого и делителя).
    dividend = dividend < 0 ? Add((~dividend), 1) : dividend;
    divisor = divisor < 0 ? Add((~divisor), 1) : divisor;

    // Осуществляем побитовое деление
    for (int i = sizeof(int) * 8 - 1; i > -1; i--)
    {
        if ((divisor << i) <= dividend)
        {
            dividend -= divisor << i;
            ans += 1 << i;
        }
    }

    // Добавляем знак к результату
    return isNegative ? -ans : ans;
}

Код 4. Cпособ разделить побитовыми операциями два целых числа.

Код 4 заимствован из статьи https://iq.opengenus.org/bitwise-division/ и адаптирован для статьи.

Первым делом хотелось бы напомнить, что свиг числа a на n бит влево — это умножение числа а на 2n.Сдвиг числа a на n бит вправо — это деление числа а на 2n. Это легко можно понять приведя аналогию с десятичной системой исчисления. Пример (в десятичной системе исчисления) со сдвигом на один порядок вправо/влево:

1024 >> 1 = 102 — деление на 10
1024 << 1 = 10240 — умножение на 10

Возьмем два числа a = 71 и b = 7. Когда мы делим a на b, мы вычисляем, сколько b может поместиться внутри a. В этом случае мы можем уместить 10 b в a, т.е. a / b = 10.(Примечание: здесь мы рассчитываем только целое значение).

Мы знаем, что каждое число можно представить как сумму степеней двойки, а также, когда мы сдвигаем число влево на n битов, оно умножается на 2 в степени n. Таким образом, мы сдвигаем делитель b влево и проверяем, меньше ли он делимого a или равен ему. Если оно меньше или равно делимому, мы вычитаем его из делимого и добавляем значение 2 степени n к нашему ответу. Таким образом, мы получим наш ответ в виде суммы степеней двойки, которая даст нам требуемое частное.

Далее опишем алгоритм естественным языком:

  1. Устанавливаем чатное (ответ) равным нулю.
  2. Получаем знак числа результата.
  3. Делаем оба числа положительными.
  4. Начинаем цикл со самого старшего бита n и продолжаем цикл до n = 0 (младшего значащего бита):
    1. Убедитесь, что сдвиг делителя на n бит меньше или равен делимому:
      • если да, вычтите это из делимого
      • Прибавьте к ответу 2 в степени n
    2. (Примечание: здесь делимое уменьшается каждый раз, когда условие выполняется.)

  5. Добавляем знак к результату из шага 2. И возвращаем результат.

Пример прохождения по алгоритму:

Анимация 4. Пример прохождения по алгоритму деления более сложных параметров.

2.5. Получение остатка от деления

Cпособ получить остаток от деления двух целых чисел побитовыми операциями:

// Получение остатка от деления
public static long DivisionRemainder(long dividend, long divisor)
{
    // Получаем знак результата
    bool isNegative = (dividend < 0) ^ (divisor < 0);

    // Получаем модуль числа (абсолютное значение) для входных параметров (делимого и делителя).
    dividend = dividend < 0 ? Add((~dividend), 1) : dividend;
    divisor = divisor < 0 ? Add((~divisor), 1) : divisor;

    // Осуществляем побитовое деление и получаем остаток
    for (int i = sizeof(int) * 8 - 1; i > -1; i--)
    {
        if ((divisor << i) <= dividend)
        {
            dividend -= divisor << i;
        }
    }

    // Добавляем знак к результату
    return isNegative ? -dividend : dividend;
}

Код 5. Cпособ получить остаток от деления двух целых чисел побитовыми операциями.

Код 5 заимствован из статьи https://iq.opengenus.org/bitwise-division/ и адаптирован для статьи.

Пример кода аналогичен алгоритму деления из которого удалены не нужные переменные.

3. Реализуем класс больших (многобайтовых) целых чисел

В данном разделе попробуем реализовать Класс целого числа без переполнения. Класс представляет собой массив байтов и реализацию основных операций над массивом байтов. Это целое число ограниченное в размере только оперативной памятью компьютера. В нём реализованы 5 математических операций рассмотреных ранее в этой статье, плюс необходимые побитовые операции и преобразования в строку, это:

Продемонстрируем далее код класса для Целых чисел без переполнения (он также доступен для скачивания на GitHub (Blog-BitMath) или по ссылке):

// Author: Dmitriy Sidyakin
// Date: 2021-10-02

using System;
using System.Collections.Generic;

namespace BitMath
{
    /// <summary>
    /// Represents an Unlimited Integer in a memory.
    /// The class is represented in RAM byte-by-byte. Integer operations are also performed.
    /// </summary>
    public class UnlimitedInteger: ICloneable
    {
        #region Fields

        /// <summary>
        /// Protected variable: The sign of Integer. Asked on the question: Is an Integer Negative?
        /// </summary>
        protected bool _isNegative;

        /// <summary>
        /// The sign of Integer. Asked on the question: Is an Integer Negative?
        /// </summary>
        public bool IsNegative
        {
            get
            {
                return _isNegative;
            }
            set
            {
                _isNegative = value;
            }
        }

        /// <summary>
        /// The Byte Representation of Integer is from the Low (the start index is 0) to the High (the end index is Lenght - 1).
        /// </summary>
        protected List<byte> _bytes;

        /// <summary>
        /// The Bytes of Integer part without sign.
        /// </summary>
        public byte[] Bytes
        {
            get
            {
                byte[] result = new byte[Length];
                if (Length > 0)
                    _bytes.CopyTo(result);
                return result;
            }
        }

        /// <summary>
        /// The Length of Integer is in bytes (byte count) without sign.
        /// </summary>
        public int Length
        {
            get
            {
                return _bytes.Count;
            }
        }

        #endregion

        #region Constructors

        public UnlimitedInteger(byte[] bytes, bool isNegative)
        {
            _bytes = new List<byte>(bytes);
            this.DeleteHighZeroBytes();
            _isNegative = isNegative;
        }

        #endregion

        #region Object methods

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (GetType() != obj.GetType())
                return false;

            UnlimitedInteger co = (UnlimitedInteger)obj;

            return this == co;
        }

        public override int GetHashCode()
        {
            byte additionalElements = 0;
            int sizeOfInt = sizeof(int);
            int additionalBytes = _bytes.Count % sizeOfInt;
            if (additionalBytes > 0)
            {
                additionalElements = 1;
            }

            int[] result = new int[_bytes.Count / sizeOfInt + additionalElements];
            int index = 0;

            while (index < result.Length)
            {
                if (index < (result.Length - 1))
                {
                    for (int i = 0; i < sizeOfInt; i++)
                    {
                        result[index] |= _bytes[index + i];

                        if (i < (sizeOfInt - 1))
                        {
                            result[index] <<= 8;
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < additionalBytes; i++)
                    {
                        result[index] |= _bytes[index + i];

                        if (i < (sizeOfInt - 1))
                        {
                            result[index] <<= 8;
                        }
                    }
                }
                ++index;
            }

            int hashCode = 0;

            for (int i = 0; i < result.Length; i++)
                hashCode ^= result[i];

            return hashCode;
        }

        #endregion

        #region Comparison Operators (x == y, x != y, x < y, x > y, x <= y, x >= y)

        public static bool operator ==(UnlimitedInteger a, UnlimitedInteger b)
        {

            if (a.Length != b.Length)
                return false;

            if (a.IsNegative != b.IsNegative)
                return false;

            bool result = true;

            int currentByte = a.Length - 1;
            while (result && currentByte >= 0)
            {
                result = a._bytes[currentByte] == b._bytes[currentByte];
                --currentByte;
            }

            return result;
        }

        public static bool operator !=(UnlimitedInteger a, UnlimitedInteger b)
        {
            return !(a == b);
        }

        public static bool operator <(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a.Length < b.Length)
                return true;
            if (a.Length > b.Length)
                return false;

            int firstDifferentByteIndexFromHigh = a.Length - 1;
            while (firstDifferentByteIndexFromHigh >= 0 && a._bytes[firstDifferentByteIndexFromHigh] == b._bytes[firstDifferentByteIndexFromHigh]) { --firstDifferentByteIndexFromHigh; }

            if (firstDifferentByteIndexFromHigh == -1)
                return false;
            else
                return a._bytes[firstDifferentByteIndexFromHigh] < b._bytes[firstDifferentByteIndexFromHigh];
        }

        public static bool operator >(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a.Length > b.Length)
                return true;
            if (a.Length < b.Length)
                return false;

            int firstDifferentByteIndexFromHigh = a.Length - 1;
            while (firstDifferentByteIndexFromHigh >= 0 && a._bytes[firstDifferentByteIndexFromHigh] == b._bytes[firstDifferentByteIndexFromHigh]) { --firstDifferentByteIndexFromHigh; }

            if (firstDifferentByteIndexFromHigh == -1)
                return false;
            else
                return a._bytes[firstDifferentByteIndexFromHigh] > b._bytes[firstDifferentByteIndexFromHigh];
        }

        public static bool operator <=(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a.Length < b.Length)
                return true;
            if (a.Length > b.Length)
                return false;

            int firstDifferentByteIndexFromHigh = a.Length - 1;
            while (firstDifferentByteIndexFromHigh >= 0 && a._bytes[firstDifferentByteIndexFromHigh] == b._bytes[firstDifferentByteIndexFromHigh] && firstDifferentByteIndexFromHigh >= 0) { --firstDifferentByteIndexFromHigh; }

            if (firstDifferentByteIndexFromHigh == -1)
                return true;
            else
                return a._bytes[firstDifferentByteIndexFromHigh] < b._bytes[firstDifferentByteIndexFromHigh];
        }

        public static bool operator >=(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a.Length > b.Length)
                return true;
            if (a.Length < b.Length)
                return false;

            int firstDifferentByteIndexFromHigh = a.Length - 1;
            while (firstDifferentByteIndexFromHigh >= 0 && a._bytes[firstDifferentByteIndexFromHigh] == b._bytes[firstDifferentByteIndexFromHigh]) { --firstDifferentByteIndexFromHigh; }

            if (firstDifferentByteIndexFromHigh == -1)
                return true;
            else
                return a._bytes[firstDifferentByteIndexFromHigh] > b._bytes[firstDifferentByteIndexFromHigh];
        }

        #endregion

        #region Delete High zero bytes

        protected void DeleteHighZeroBytes()
        {
            int highIndex = _bytes.Count - 1;

            while (highIndex > 0 && _bytes[highIndex] == 0)
            {
                --highIndex;
            }

            _bytes.RemoveRange(highIndex + 1, _bytes.Count - highIndex - 1);
        }

        #endregion

        #region Bitwise Operators (x & y, x | y, x ^ y, x << y, x >> y)

        public static UnlimitedInteger operator &(UnlimitedInteger a, UnlimitedInteger b)
        {
            int minLength = a.Length > b.Length ? b.Length : a.Length;

            byte[] result = new byte[minLength];

            for (int i = 0; i < minLength; i++)
            {
                result[i] = (byte)(a._bytes[i] & b._bytes[i]);
            }

            UnlimitedInteger resultUnlimited = new UnlimitedInteger(result, a.IsNegative & b.IsNegative);
            resultUnlimited.DeleteHighZeroBytes();

            return resultUnlimited;
        }

        /// <summary>
        /// Getting min and max values from (a,b). Exception: if a==b then min = a, max = b.
        /// </summary>
        /// <param name="a">First comparable element</param>
        /// <param name="b">Second comparable element</param>
        /// <param name="min">Minimum from elements</param>
        /// <param name="max">Maximum from elements</param>
        private static void GetMinMax(UnlimitedInteger a, UnlimitedInteger b, out UnlimitedInteger min, out UnlimitedInteger max)
        {
            if (a > b)
            {
                min = b; max = a;
            }
            else
            {
                min = a; max = b;
            }
        }

        public static UnlimitedInteger operator |(UnlimitedInteger a, UnlimitedInteger b)
        {
            UnlimitedInteger min, max;
            GetMinMax(a, b, out min, out max);

            byte[] result = new byte[max.Length];

            int copiedIndex = 0;
            for (int i = 0; i < min.Length; i++)
            {
                result[i] = (byte)(min._bytes[i] | max._bytes[i]);
                ++copiedIndex;
            }

            for (int i = copiedIndex; i < max.Length; i++)
            {
                result[i] = max._bytes[i];
            }

            UnlimitedInteger resultUnlimited = new UnlimitedInteger(result, a.IsNegative | b.IsNegative);
            resultUnlimited.DeleteHighZeroBytes();

            return resultUnlimited;
        }

        public static UnlimitedInteger operator ^(UnlimitedInteger a, UnlimitedInteger b)
        {
            UnlimitedInteger min, max;
            GetMinMax(a, b, out min, out max);

            byte[] result = new byte[max.Length];

            int copiedIndex = 0;
            for (int i = 0; i < min.Length; i++)
            {
                result[i] = (byte)(min._bytes[i] ^ max._bytes[i]);
                ++copiedIndex;
            }

            for (int i = copiedIndex; i < max.Length; i++)
            {
                result[i] = max._bytes[i];
            }

            UnlimitedInteger resultUnlimited = new UnlimitedInteger(result, a.IsNegative ^ b.IsNegative);
            resultUnlimited.DeleteHighZeroBytes();

            return resultUnlimited;
        }

        public static UnlimitedInteger operator <<(UnlimitedInteger a, int b)
        {

            if (b == 0)
            {
                return (UnlimitedInteger)a.Clone();
            }

            if (b < 0)
                return a >> Math.Abs(b);

            UnlimitedInteger result;

            int additionalBytes = b / 8;
            int shift = b % 8;

            if (shift > 0)
            {
                additionalBytes++;
            }

            int size = a.Length + additionalBytes;

            result = new UnlimitedInteger(a.Bytes, a.IsNegative);
            for (int i = 0; i < additionalBytes; i++)
                result._bytes.Insert(0, 0);

            if (shift > 0)
            {
                int unshift = 8 - shift;

                result >>= unshift;
            }

            result.DeleteHighZeroBytes();

            return result;
        }

        public static UnlimitedInteger operator >>(UnlimitedInteger a, int b)
        {
            if (b == 0)
            {
                return (UnlimitedInteger)a.Clone();
            }
            if (b == 8)
            {
                ;
            }

            if (b < 0)
                return a << Math.Abs(b);

            UnlimitedInteger result;

            int additionalBytes = b / 8;
            int shift = b % 8;

            int size = 1;
            if (a.Length > 1)
                size = a.Length - additionalBytes;

            byte[] newResult = new byte[size];
            for (int i = additionalBytes, j = 0; i < a.Length; i++, j++)
            {
                newResult[j] = a._bytes[i];
            }

            result = new UnlimitedInteger(newResult, a.IsNegative);

            if (shift != 0)
            {
                for (int i = 1; i < result.Length; i++)
                {
                    result._bytes[i - 1] = (byte)((result._bytes[i - 1]) >> shift);
                    result._bytes[i - 1] = (byte)((result._bytes[i] << (8 - shift)) | result._bytes[i - 1]);
                }

                result._bytes[result.Length - 1] >>= shift;
            }

            result.DeleteHighZeroBytes();

            return result;
        }

        #endregion

        #region Unary Operators (+x, -x, !x, ~x, ++, --, true, false)

        public static UnlimitedInteger operator +(UnlimitedInteger x)
        {
            return (UnlimitedInteger)x.Clone();
        }

        public static UnlimitedInteger operator -(UnlimitedInteger x)
        {
            UnlimitedInteger result = new UnlimitedInteger(x.Bytes, !x.IsNegative);
            return result;
        }

        public static bool operator true(UnlimitedInteger x)
        {
            bool result = true;
            if (x.Length == 1 && x._bytes[0] == 0)
                result = false;
            return result;
        }

        public static bool operator false(UnlimitedInteger x)
        {
            bool result = false;
            if (x.Length == 1 && x._bytes[0] == 0)
                result = true;
            return result;
        }

        public static bool operator !(UnlimitedInteger x)
        {
            bool result = true;
            if (x)
                result = false;
            return result;
        }

        public static UnlimitedInteger operator ~(UnlimitedInteger x)
        {
            UnlimitedInteger result = new UnlimitedInteger(x.Bytes, !x.IsNegative);

            for (int index = 0; index < result.Length; index++)
            {
                result._bytes[index] = (byte)~result._bytes[index];
            }

            result.DeleteHighZeroBytes();

            return result;
        }


        public static UnlimitedInteger operator ++(UnlimitedInteger x)
        {
            UnlimitedInteger result;

            if (x.IsNegative)
            {
                if (!x)
                    result = One();
                else
                {
                    result = UnlimitedInteger.SubPositive(x, One());
                    result.IsNegative = true;
                }
            }
            else
            {
                if (!x)
                    result = One();
                else
                {
                    result = UnlimitedInteger.AddPositive(x, One());
                    result.IsNegative = false;
                }
            }

            result.DeleteHighZeroBytes();

            return result;
        }

        public static UnlimitedInteger operator --(UnlimitedInteger x)
        {
            UnlimitedInteger result;

            if (x.IsNegative)
            {
                if (!x)
                    result = MinusOne();
                else
                {
                    result = UnlimitedInteger.AddPositive(x, One());
                    result.IsNegative = true;
                }
            }
            else
            {
                if (!x)
                    result = MinusOne();
                else
                {
                    result = UnlimitedInteger.SubPositive(x, One());
                    result.IsNegative = false;
                }
            }

            result.DeleteHighZeroBytes();

            return result;
        }

        #endregion

        #region Maths Operations (x + y, x - y, x * y, x / y, x % y)

        private static UnlimitedInteger AddPositive(UnlimitedInteger summand, UnlimitedInteger added)
        {
            int maskLenght = summand.Length > added.Length ? summand.Length : added.Length;
            maskLenght++;
            byte[] maskBytes = new byte[maskLenght];
            for (int i = 0; i < (maskLenght - 1); i++)
            {
                maskBytes[i] = 0xFF;
            }
            maskBytes[maskLenght - 1] = 0b0000_0001;

            UnlimitedInteger carry = Zero(),
                myAdded = (UnlimitedInteger)added.Clone(),
                mySummand = (UnlimitedInteger)summand.Clone(),
                mask = new UnlimitedInteger(maskBytes, false);

            while (myAdded.Length > 1 || (myAdded.Length == 1 && myAdded._bytes[0] != 0))
            {
                carry = (mySummand & myAdded);

                mySummand = mySummand ^ myAdded;

                myAdded = (carry << 1) & mask;
            }

            return mySummand;
        }

        private static UnlimitedInteger SubPositive(UnlimitedInteger minuend, UnlimitedInteger subtrahend)
        {
            if (minuend < subtrahend)
                throw new ArgumentOutOfRangeException("Minuend must be higher or equal subtrahend");

            int maskLenght = minuend.Length;

            byte[] maskBytes = new byte[maskLenght];
            for (int i = 0; i < maskLenght; i++)
            {
                maskBytes[i] = 0xFF;
            }


            UnlimitedInteger borrow = Zero(),
                myMinuend = (UnlimitedInteger)minuend.Clone(),
                mySubtrahend = (UnlimitedInteger)subtrahend.Clone(),
                mask = new UnlimitedInteger(maskBytes, false);


            while (mySubtrahend.Length > 1 || (mySubtrahend.Length == 1 && mySubtrahend._bytes[0] != 0))
            {

                borrow = ((~myMinuend) & mySubtrahend);

                myMinuend = myMinuend ^ mySubtrahend;

                mySubtrahend = (borrow << 1) && mask;
            }

            return myMinuend;
        }

        public static UnlimitedInteger operator +(UnlimitedInteger a, UnlimitedInteger b)
        {
            UnlimitedInteger result;

            // Если у них разные знаки
            if (a.IsNegative ^ b.IsNegative)
            {
                // ... то делаем вычитание ...
                if (a._isNegative)
                {
                    if (a > b)
                    {
                        result = SubPositive(a, b);
                        result._isNegative = true;
                    }
                    else
                    {
                        result = SubPositive(b, a);
                        result._isNegative = false;
                    }
                }
                else
                {
                    if (b > a)
                    {
                        result = SubPositive(b, a);
                        result._isNegative = true;
                    }
                    else
                    {
                        result = SubPositive(a, b);
                        result._isNegative = false;
                    }
                }
            }
            else
            {
                // ... иначе сложение
                result = AddPositive(a, b);
                result._isNegative = a._isNegative;
            }

            return result;
        }

        public static UnlimitedInteger operator -(UnlimitedInteger a, UnlimitedInteger b)
        {
            UnlimitedInteger result;

            // Если у них разные знаки
            if (a.IsNegative ^ b.IsNegative)
            {
                // ... иначе сложение
                result = AddPositive(a, b);
                result._isNegative = a._isNegative;

            }
            else
            {
                // ... то делаем вычитание ...
                if (a._isNegative)
                {
                    if (a > b)
                    {
                        result = SubPositive(a, b);
                        result._isNegative = true;
                    }
                    else
                    {
                        result = SubPositive(b, a);
                        result._isNegative = false;
                    }
                }
                else
                {
                    if (b > a)
                    {
                        result = SubPositive(b, a);
                        result._isNegative = true;
                    }
                    else
                    {
                        result = SubPositive(a, b);
                        result._isNegative = false;
                    }
                }
            }

            return result;
        }

        public static UnlimitedInteger operator *(UnlimitedInteger a, UnlimitedInteger b)
        {
            // Результат.
            UnlimitedInteger result = Zero();

            UnlimitedInteger mul1 = (UnlimitedInteger)a.Clone(), mul2 = (UnlimitedInteger)b.Clone();

            mul1.IsNegative = false;
            mul2.IsNegative = false;

            // Пока второй множитель не равен нулю.
            while (mul2 != Zero())
            {
                // Если установлен бит в очередном разряде...
                if ((mul2 & One()) == One())
                {
                    // сложить первый множитель с самим собою.
                    result = AddPositive(result, mul1);
                }

                // Очередной сдвиг первого множителя влево.
                mul1 <<= 1;

                // Подводим очередной разряд в начало для сверки с условием оператора if().
                mul2 >>= 1;
            }

            result.IsNegative = a.IsNegative ^ b.IsNegative;

            return result;
        }
        public static UnlimitedInteger operator /(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a < b)
            {
                return Zero();
            }
            if (a == b)
            {
                return One();
            }
            if (b == Zero())
            {
                throw new DivideByZeroException();
            }

            // Результат.
            UnlimitedInteger result = Zero();

            UnlimitedInteger dividend = (UnlimitedInteger)a.Clone(), divisor = (UnlimitedInteger)b.Clone();

            dividend.IsNegative = false;
            divisor.IsNegative = false;

            int length = a.Length * 8 - 1;

            for (int i = length; i > -1; i--)
            {
                if ((divisor << i) <= dividend)
                {
                    dividend -= divisor << i;
                    result += One() << i;
                }
            }

            result.IsNegative = a.IsNegative ^ b.IsNegative;
            return result;
        }

        private static int GetLastOneBitPossition(UnlimitedInteger a)
        {
            for (int i = a.Length * 8 - 1; i > -1; i--)
            {
                if (a >> i != Zero())
                {
                    return i - 1;
                }
            }

            return -1;
        }

        private static bool IsMinus(UnlimitedInteger a, UnlimitedInteger b)
        {
            if ((a && b) == b)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public static UnlimitedInteger operator %(UnlimitedInteger a, UnlimitedInteger b)
        {
            return Remainder(a, b);
        }

        private static UnlimitedInteger Remainder(UnlimitedInteger a, UnlimitedInteger b)
        {
            if (a < b)
            {
                return (UnlimitedInteger)a.Clone();
            }
            if (a == b)
            {
                return Zero();
            }
            if (b == Zero())
            {
                throw new DivideByZeroException();
            }

            // Результат.
            UnlimitedInteger result = Zero();

            UnlimitedInteger dividend = (UnlimitedInteger)a.Clone(), divisor = (UnlimitedInteger)b.Clone();

            dividend.IsNegative = false;
            divisor.IsNegative = false;

            int length = a.Length * 8 - 1;

            for (int i = length; i > -1; i--)
            {
                if ((divisor << i) <= dividend)
                {
                    dividend -= divisor << i;
                    result += One() << i;
                }
            }

            dividend.IsNegative = a.IsNegative ^ b.IsNegative;
            return dividend;
        }

        #endregion

        #region Math Functions

        public bool IsEven()
        {
            return (this && One()) == Zero();
        }

        public bool IsOdd()
        {
            return (this && One()) == One(); ;
        }

        #endregion

        #region ToString Operations

        public string ToString(byte @base)
        {
            if (!(@base == 2 || @base == 8 || @base == 10 || @base == 16))
                throw new ArgumentOutOfRangeException("@base", "Avaliable values: 2, 8, 10, 16");

            string result = String.Empty;

            if (@base != 10)
            {
                if (@base == 2)
                {
                    for (int i = 0; i < _bytes.Count; i++)
                    {
                        result = Convert.ToString((int)(_bytes[i]) ^ 0b100000000, @base).Substring(1, 8) + result;
                    }
                    result = TrimLeftWithChar(result, '0');

                    if (result.Length == 0)
                        result = "0";
                }

                if (@base == 16)
                {
                    for (int i = 0; i < _bytes.Count; i++)
                    {
                        result = Convert.ToString(_bytes[i] ^ 0b100000000, @base).Substring(1, 2) + result;
                    }
                    result = TrimLeftWithChar(result, '0');

                    if (result.Length == 0)
                        result = "0";
                }

                if (@base == 8)
                {
                    UnlimitedInteger forOctal = (UnlimitedInteger)Clone();

                    while (forOctal.Length > 1)
                    {
                        result = Convert.ToString(forOctal._bytes[0] & 0b00000111, @base).Substring(0, 1) + result;

                        forOctal >>= 3;
                    }

                    byte lastByte = forOctal._bytes[0];

                    while (lastByte != 0)
                    {
                        while (lastByte != 0)
                        {
                            result = Convert.ToString(lastByte & 0b00000111, @base).Substring(0, 1) + result;

                            lastByte >>= 3;
                        }
                    }
                }
            }
            else
            {
                UnlimitedInteger dec = (UnlimitedInteger)Clone();
                dec.IsNegative = false;
                while (dec != Zero())
                {
                    result = (dec % Ten()).Bytes[0].ToString() + result;
                    dec = dec / Ten();
                }

                if (String.IsNullOrEmpty(result))
                {
                    result = "0";
                }
            }

            if (IsNegative)
            {
                result = "-" + result;
            }

            return result;
        }

        private static string TrimLeftWithChar(string input, char chr)
        {
            int count = 0;

            while (count < input.Length && input[count] == chr) count++;

            return input.Substring(count);
        }

        #endregion

        #region ICloneable Interface

        public object Clone()
        {
            UnlimitedInteger result = new UnlimitedInteger(this.Bytes, this.IsNegative);
            return result;
        }

        #endregion

        #region Constants

        public static UnlimitedInteger One()
        {
            return new UnlimitedInteger(new byte[1] { 1 }, false);
        }

        public static UnlimitedInteger MinusOne()
        {
            return new UnlimitedInteger(new byte[1] { 1 }, true);
        }

        public static UnlimitedInteger Zero()
        {
            return new UnlimitedInteger(new byte[1] { 0 }, false);
        }

        public static UnlimitedInteger Ten()
        {
            return new UnlimitedInteger(new byte[1] { 10 }, false);
        }

        #endregion

        #region Parse

        public static UnlimitedInteger Parse(string str)
        {
            int startIndex = 0;

            bool isNeg = false;
            if (str[0] == '-')
            {
                isNeg = true;
                startIndex++;
            }

            UnlimitedInteger result = UnlimitedInteger.Zero();
            UnlimitedInteger j = UnlimitedInteger.One();
            for (int i = str.Length - 1; i >= startIndex; i--, j *= UnlimitedInteger.Ten())
            {
                byte digit = byte.Parse(str[i].ToString());
                UnlimitedInteger uiDigit = new UnlimitedInteger(new byte[] { digit }, false);
                result += uiDigit * j;
            }

            result.IsNegative = isNeg;

            return result;
        }

        #endregion
    }
}

Тестирование работы кода и отдельных методов Вы можете проверить самостоятельно (код доступен для скачивания на GitHub (Blog-BitMath) или по ссылке).

4. Заключение

В заключение хочу Вам пожелать написание своей библиотеки работы с любыми числами любой точности на основе побитовых математических операций в статье. Возможно на основе целых реализовать вещественные числа.

Код класса и примеров в статье доступен на GitHub (Blog-BitMath) или по ссылке.