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

в 7:14, , рубрики: Без рубрики, метки:

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

Источник


  1. Владимир:

    Спасибо большое пригодилось…

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js