Недавно появилась у меня задача, в ходе решения которой необходимо было считывать из бинарного файла целые числа различного формата. Хорошо было бы если бы это были 8, 16, 32 или 64-разрядные знаковые или беззнаковые числа, тогда можно было сразу без проблем считать из файла значение нужного формата, но на практике пришлось иметь дело со знаковыми и беззнаковыми числами произвольной разрядности (от 2 до 64 разрядов на число, т.е., например, в 8 байтах могут располагаться 2 числа, первое с 0 разряда по 28, а второе с 29 по 63 разряды, при этом первое знаковое, а второе беззнаковое).Если интересно увидеть мои скромные наработки по работе с битами-прошу под кат.
Немного поискав решение данной проблемы в интернете, я пришел к выводу, что быстрее будет самому решить данную проблему.
Отмечу, что также была необходимость смотреть и изменять значения отдельных битов (это можно сделать с помощью битовых флагов или класса BitArray, но я решил действовать по-другому), еще имелась необходимость реверсировать порядок битов.
Итак, имея немного опыта программирования за плечами, я решил реализовать все описанные выше функции в статическом классе.
Для просмотра и изменения значения отдельных битов, я реализовал 2 статических метода GetBitи SetBit, первый в качестве параметров принимает байт у которого необходимо посмотреть значение бита и, собственно, сам номер бита, во втором добавляется еще и значение устанавливаемого бита в виде булевской переменной. Реализация методов достаточно тривиальна:
///<summary>
/// Возвращает бит в num байте val
///</summary>
///<param name="val">Входнойбайт</param>
///<param name="num">Номербита, начинаяс 0</param>
///<returns>true-битравен 1, false- битравен 0</returns>
public static bool GetBit(byte val,int num)
{
if ((num> 7) || (num< 0))//Проверка входных данных
{
throw new ArgumentException();
}
return ((val>>num)&1)>0;//собственно все вычисления
}
///<summary>
/// Устанавливает значение определенного бита в байте
///</summary>
///<param name="val">Входнойбайт</param>
///<param name="num">Номербита</param>
///<param name="bit">Значениебита: true-битравен 1, false- битравен 0 </param>
///<returns>Байт, с измененным значением бита</returns>
public static byte SetBit(byte val, int num,bool bit)
{
if ((num> 7) || (num< 0))//Проверка входных данных
{
throw new ArgumentException();
}
byte tmpval = 1;
tmpval = (byte)(tmpval<<num);//устанавливаем нужный бит в единицу
val = (byte)(val& (~tmpval));//сбрасываем в 0 нужный бит
if (bit)// если бит требуется установить в 1
{
val = (byte)(val | (tmpval));//то устанавливаем нужный бит в 1
}
return val;
}
Не заморачиваясь поиском модных и быстрых алгоритмов по реверсу битов, сделал следующее:
///<summary>
/// Изменяет порядок битов на обратный
///</summary>
///<param name="val">Входнойбайт</param>
///<returns>Байт с обратным порядком битов</returns>
Public static byte Reverse(byte val)
{
int i = 0;
byterez = 0;
for (i = 0; i < 8; i++)
{
rez = (byte)(rez<< 1);
if (((val>> i) & 1) > 0)
{
rez = (byte)(rez | 1);
}
}
return rez;
}
Но самое интересное было приведение чисел произвольной разрядности к виду понятному машине, в случае если бы они все были беззнаковыми, проблем бы не было- считал нужное количество байтов, преобразовал в нужный формат и отсек ненужные биты, но так как встречались как знаковые так и беззнаковые числа пришлось немного выкручиваться, т.к. знаковый бит не будет понят как знаковый и числа сами по себе хранятся в дополнительном коде:
///<summary>
/// Преобразует массив байтов в Int64, начиная с бита firstbit и заканчивая битом lastbit, с учетом знаковости числа
///</summary>
///<paramname="bytes">Массив байтов, начиная со старшей части числа</param>
///<param name="firstbit">Младшийбитчисла</param>
///<param name="lastbit">Старшийбитчисла</param>
///<paramname="issigned">Показывает со знаком ли число</param>
///<returns>Результат преобразования Int64</returns>
public static Int64 HandleBytes(byte[] bytes, int firstbit, int lastbit,bool issigned)
{
if ((bytes == null) ||
(firstbit< 0) ||
(firstbit> 63) ||
(lastbit< 0) ||
(lastbit> 63) ||
(((lastbit - firstbit) == 63) && (!issigned)) ||//если число без знака и при этом 64-разрядное, то такое число физически нельзя вернуть в Int64
(bytes.Length> 8) ||
(bytes.Length< 1)||
(firstbit>lastbit))
{
throw new ArgumentException();
}
Int64 rezult = 0,//переменная для результата
tmp = 0;//переменная для промежуточных вычислений
//Заполняем переменную результата входным массивом байтов
foreach (byte tmp1 in bytes)
{
rezult = rezult<< 8;
rezult = rezult | tmp1;
}
rezult = rezult>>firstbit;//отсекаем ненужные младшиебиты
if (firstbit != lastbit)
{
tmp = (long)(Math.Pow(2, lastbit - firstbit));//1 в знаковом бите, остальные 0
if ((issigned)&&((rezult&tmp)>0))//если число знаковое и отрицательно
{
//это все необходимо потому что числа сами по себе записываются в дополнительном коде
tmp = tmp - 1;//все 1 начиная с младшего бита и до знакового бита, не включая знаковый бит
rezult = ~rezult;// инвертируем биты
rezult = rezult&tmp;// отсекам старшие ненужные биты
rezult++;
rezult *=-1;
}
else// если число беззнаковое или знаковое, но имеет положительное значение
{
tmp = tmp + (tmp - 1);//все 1 начиная с младшего бита и до знакового бита, включая знаковый бит
rezult = rezult&tmp;//отсекаем ненужные биты
}
}
else//Если младший и старший биты равны, то нужно просто вернуть один бит
{
rezult = rezult& 1;
}
return rezult;
}
В качестве параметров метод принимает массив байтов, количество байт от 1 до 8, старшая часть в 0. firstbit –номер младшего бита числа, lastbit –номер старшего бита числа, issigned- указывает знаковое число или беззнаковое. Таким образом если число знаковое и располагается в разрядах 3-18, то вызов метода будет выглядеть HandleBytes(bytes, 3, 18,true).
Первые 3 метода в качестве входных параметров имеют байт, естественно, по необходимости можно легко сделать перегруженные версии для других типов параметров.
Надеюсь хоть кому-то это будет хоть немного полезно.
Автор: hukuta1
Спасибо большое пригодилось…