Работа с битами в языке C#

от автора

Недавно появилась у меня задача, в ходе решения которой необходимо было считывать из бинарного файла целые числа различного формата. Хорошо было бы если бы это были 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 метода в качестве входных параметров имеют байт, естественно, по необходимости можно легко сделать перегруженные версии для других типов параметров.

Надеюсь хоть кому-то это будет хоть немного полезно.

ссылка на оригинал статьи http://habrahabr.ru/post/156021/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *