Как получить наименьшее количество байтов, необходимых для хранения целочисленного значения с помощью макроса?

#c #macros

#c #макросы

Вопрос:

Например, если целое число меньше 255, его можно восстановить за 1 байт,

если оно больше 255, требуется не менее 2 байт.

Как написать такой BYTES_REQUIRED(i) макрос?

Комментарии:

1. Почему макрос? Вы знаете, как это сделать с помощью обычной функции?

2. @Mat , на самом деле я могу думать только об этом: if(i<255)return 1;else if(i>255 amp;amp; i < 2^16)return 2;... , который слишком длинный, чтобы быть макросом.

3. Зачем вам нужен макрос?

4. @Mat , в моем случае i все это константы, мне нужен макрос, чтобы результат можно было вычислить во время компиляции, без накладных расходов во время выполнения.

5. И каков диапазон ваших i s? 32 бит? 64 бит? Еще?

Ответ №1:

Если вы используете компилятор C99, выполните приведение ниже (unsigned long long) . Также вы можете (и должны) расширить конструкцию до 8 или 16 байт (это оставлено в качестве упражнения)

 #include <limits.h>

#define BYTES_REQUIRED(i)                            
           !((unsigned long)(i) >>     CHAR_BIT) ? 1 
         : !((unsigned long)(i) >> 2 * CHAR_BIT) ? 2 
         : !((unsigned long)(i) >> 3 * CHAR_BIT) ? 3 
         :                                         4
  

Комментарии:

1. Зачем мне нужно приводить его к unsigned long long, если это компилятор c99?

2. Потому unsigned long long что не был распознан C89, и он, по крайней мере, такой же большой, как unsigned long . Использование unsigned long long ничего не теряет (кроме совместимости с C89) и, возможно, получает более широкий диапазон рабочих значений.

Ответ №2:

При этом используется эффективный подход «разделяй и властвуй»:

 #define BYTES_REQUIRED(i) (((i) amp; 0xFFFF0000) ? (((i) amp; 0xFF000000) ? 4 : 3) : (((i) amp; 0xFF00) ? 2 : 1))
  

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

 #define BYTES_REQUIRED(i) (((i) amp; 0xFFFF0000) ? 4 : (((i) amp; 0xFF00) ? 2 : 1))
  

Имейте в виду, что ни один из них не обрабатывает отрицательные числа, поскольку он видит знак extended 1 бит как используемое пространство. Для этого требуется другое условие для учета (например, если отрицательно, отрицание).

Ответ №3:

Фактически вам нужно вычислить log2 (i). Нет тривиального способа сделать это переносимо, быстро, для максимального целочисленного значения, поддерживаемого компилятором и с помощью макроса.

Опции:

1. Вычислите логарифм в цикле:

 // 64 -bit version:
unsigned long BYTES_REQUIRED(unsigned long long i)
{
  unsigned long bits = 0;
  while (i)
  {
    i >>= 1;
    bits  ;
  }
  if (bits == 0) bits = 1;
  return (bits   7) / 8; // we're assuming that byte=8 bits, but CHAR_BIT may be > 8
}
  

2. Используйте встроенную функцию (фактически, выделенную инструкцию процессора) компилятора, если таковая имеется. Для MSVC :

 // 64-bit version, not available for 32-bit code:
unsigned long BYTES_REQUIRED(unsigned long long i)
{
  unsigned long index;
  if (_BitScanReverse64(amp;index, i) == 0)
  {
    index = 1;
  }
  return (index   8) / 8;
}

// 32-bit version, available for 32 and 64-bit code:
unsigned long BYTES_REQUIRED(unsigned long i)
{
  unsigned long index;
  if (_BitScanReverse(amp;index, i) == 0)
  {
    index = 1;
  }
  return (index   8) / 8;
}

// 64-bit version available for 32 and 64-bit code:
unsigned long BYTES_REQUIRED(unsigned long long i)
{
  unsigned long index;
  if (_BitScanReverse(amp;index, (unsigned long)(i >> 32)))
  {
    index  = 32;
  }
  else if (_BitScanReverse(amp;index, (unsigned long)i) == 0)
  {
    index = 1;
  }
  return (index   8) / 8;
}
  

3. Используйте if или ?: зная размер самого большого поддерживаемого целочисленного типа… Другие уже описали этот метод.

Комментарии:

1. Решение log-base-2 дает точность бита, которая не нужна. Вы решаете более сложную проблему, чем он спросил, а затем делите дополнительную точность. _BitScanReverse может иметь некоторый смысл, поскольку он быстр на некоторых процессорах, но он попросил что-то, что разрешается во время компиляции, поэтому производительность в любом случае не имеет значения. Instrinsic делает код менее переносимым и в обмен ни на что.

Ответ №4:

К сожалению, вы запросили макрос C, потому что эта шаблонная функция C могла бы быть полезной (она должна работать для любого целочисленного типа, поддерживаемого вашим компилятором).

 template <typename T> int bytesRequired(T value) {
  boost::function_requires< boost::IntegerConcept<T> >();
  for (int i=0; i<=sizeof(T); i  , value/=256)
    if (value == 0) return i;
}
  

Другой подход, который должен быть быстрее (потому что он не имеет ветвей), если вам не нужна просто оценка во время компиляции, — это bitscan, о котором упоминал Алекс.