Набор Алгоритмов
Статья содержит побитовое представление базовых математических операций и класс для представления неограниченных целых чисел максимальным и минимальным значением.
Алгоритмы - Побитовые операции. Побитовое описание математических операций над целыми числами
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 к нашему ответу. Таким образом, мы получим наш ответ в виде суммы степеней двойки, которая даст нам требуемое частное.
Далее опишем алгоритм естественным языком:
- Устанавливаем чатное (ответ) равным нулю.
- Получаем знак числа результата.
- Делаем оба числа положительными.
- Начинаем цикл со самого старшего бита n и продолжаем цикл до n = 0 (младшего значащего бита):
- Убедитесь, что сдвиг делителя на n бит меньше или равен делимому:
- если да, вычтите это из делимого
- Прибавьте к ответу 2 в степени n
(Примечание: здесь делимое уменьшается каждый раз, когда условие выполняется.)
- Добавляем знак к результату из шага 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 математических операций рассмотреных ранее в этой статье, плюс необходимые побитовые операции и преобразования в строку, это:
- Поля:
- bool IsNegative — Знак числа, отвечает на вопрос "Это число отрицательное?";
- byte[] Bytes — Массив байтов числа, от нулевого (младшего) до конечного (старшего);
- int Length — Количество байт в числе. Старшие нулевые байты влегда не хранятся и удаляются.
- Конструктор числа public UnlimitedInteger(byte[] bytes, bool isNegative). Принимает на вход значение числа в байтах, от младшего к старшему (byte[] bytes) и знак числи (bool isNegative)
- Переопределённые методы объекта (класса Object):
- Метод удаления старших нулевых байтов (лишних) protected void DeleteHighZeroBytes()
- Операторы сравнения (x == y, x != y, x < y, x > y, x <= y, x >= y)
- Побитовые операторы (x & y, x | y, x ^ y, x << y, x >> y)
- Унарные операторы (+x, -x, !x, ~x, ++, --, true, false)
- Математические операции (x + y, x - y, x * y, x / y, x % y)
- Признаки чётности (public bool IsEven()) и нечётности (public bool IsOdd())
- Операции преобразования в строку public string ToString(byte @base). Параметр @base - вид системы исчисления, доступные значения: 2, 8, 10, 16.
- Реализация интерфейса ICloneable. Метода public object Clone().
- Методы возвращающие константы (Заранее определённые числа):
- 1 — One();
- -1 — MinusOne();
- 0 — Zero();
- 10 — Ten().
- Метод преобразование строки во внутренний формат (public static UnlimitedInteger Parse(string str)).
Продемонстрируем далее код класса для Целых чисел без переполнения (он также доступен для скачивания на 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. Заключение
В заключение хочу Вам пожелать написание своей библиотеки работы с любыми числами любой точности на основе побитовых математических операций в статье. Возможно на основе целых реализовать вещественные числа.