Является ли это идиоматическим способом C для преобразования длинных в двоичное (char *) представление? - PullRequest
2 голосов
/ 10 августа 2009

Вопрос в названии, я думаю.

Это временное решение, которое я придумал, но мне было интересно:

  • Если есть недостатки при представлении двоичного файла как char *. Есть ли лучший способ (учитывая, что я хотел бы иметь возможность сдвигать биты и т. Д.)
  • Если в приведенном ниже коде есть очевидные неидиоматические C (или другие ошибки)

Все предложения приветствуются ...

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* compile with 
    gcc -lm -std=c99 
*/

void binary_repr(unsigned long input) {
    int needed_digits = (int) (floor(log2(input)) + 1);
    char *ptr_binarray = malloc((needed_digits + 1) * sizeof (char));
    int idx = (needed_digits);

    if (ptr_binarray == NULL) {
            printf("Unable to allocate memory.");
        exit(1);
    } 
    else {
        do {
            idx--;
            if (input % 2 == 0) { 
                ptr_binarray[idx] = '0'; 
            } 
            else { 
                ptr_binarray[idx] = '1'; 
            }
            input = input / 2;

        } while (input > 0);

        ptr_binarray[needed_digits] = '\0';
        printf("%s\n", ptr_binarray);
        free(ptr_binarray);
        ptr_binarray = NULL;
    }
}

int main()
{
    binary_repr(8);
    binary_repr(14);
    binary_repr(4097);
    return 0;
}

Ответы [ 8 ]

7 голосов
/ 10 августа 2009

Выглядит для меня примерно идиоматично, за исключением того, что я бы написал цикл примерно так:

char *writeptr = ptr_binarray + needed_digits;
*writeptr = 0;
do {
    --writeptr;
    *writeptr = (input % 2) + '0';
    input /= 2;
} while (input > 0);

Нет необходимости в целочисленном индексе.

В этом конкретном случае я бы не стал беспокоиться о malloc, поскольку вы free выполняете ту же функцию. Просто выделите достаточно большой массив символов в стеке:

char binarray[sizeof(unsigned long)*CHAR_BIT + 1];

Или используйте массивы переменной длины C99:

char binarray[needed_digits + 1];

Кроме того, если вы используете только gcc, то вместо логарифма вы можете использовать __builtin_clz для вычисления needed_digits. Это не о идиоматическом C, так как это диалект gcc. Но даже без этого вам не нужна математика с плавающей запятой, чтобы выяснить, сколько цифр необходимо:

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

Только что заметил возможную ошибку и в этой строке - ваш цикл do / while аккуратно обрабатывает случай, когда input равен 0, а первая строка - нет, поскольку вы не можете взять журнал 0.

Есть ли лучший способ (учитывая, что я хотел бы иметь возможность сдвигать биты и т.д. ...)

Не уверен, что вы имеете в виду здесь. Если вы хотите выполнять такие операции, как сдвиг битов над значением, не конвертируйте его в строку, подобную этой. Сохраните его как long int, и сделайте там сдвиг битов.

Другие мелочи, так как вы спрашиваете общее мнение. Ничего из этого я бы не стал критиковать, если у вас есть причина, по которой вы это сделали:

  • Уберите бессмысленных паренов вокруг (needed_digits), это просто шум.
  • Сообщение об ошибке, вероятно, должно идти в stderr, а не в stdout.
  • Я бы всегда сразу проверял возвращаемое значение из malloc (или любой другой функции, которая возвращает значение ошибки), а не между строкой кода. Поэтому переместите строку int idx = needed_digits вниз непосредственно перед циклом «do .. while» (поскольку вы используете std = c99. Если это был c89, то вы все равно можете это сделать, за исключением того, что я собираюсь рекомендовать .. .).
  • Я бы не ставил "else" после условного выхода или возврата. Но другие люди поступят так же, как и вы, и спор, возможно, станет племенным.
  • Лично я не умножил бы на sizeof(char) в malloc, так как размер буфера, выделенного malloc, измеряется в символах по определению. Но другие помещают это так, чтобы у каждого malloc всегда был размер sizeof, поэтому я снова не могу утверждать, что мой путь идиоматичен. Это просто лучше; -)
  • Очистка указателей после освобождения, возможно, имеет смысл, когда они в структуре, но не так много для автоматики.

Для каждой из трех последних вещей, хорошая практика программирования на С не обязательно должна быть такой же, как я, а согласовывать стиль кодирования с вашими коллегами / сотрудниками. Разрешается использовать стандарт кодирования «делай как хочешь», лишь бы ты согласился не спорить и не «приводить в порядок» код друг друга.

4 голосов
/ 10 августа 2009

Нет необходимости «конвертировать» числа в двоичное представление; они уже представлены в двоичном виде в памяти. Используя побитовые операторы, довольно просто играть с двоичным представлением:

#include <limits.h>
#include <stdio.h>

static void binary_repr(unsigned long input);

int main (void)
{
    binary_repr(0);
    binary_repr(1);
    binary_repr(16);

    return 0;
}

static void binary_repr(unsigned long input)
{
    unsigned int i;
    unsigned int nb_bits = sizeof(input) * CHAR_BIT;

    for (i = 0; i < nb_bits; ++i)
    {
        /* print the left-most bit */
        putchar((input & (1 << (nb_bits - 1))) == 0 ? '0' : '1');
        /* left-shift by onex */
        input <<= 1;
    }

    putchar('\n');
}
3 голосов
/ 10 августа 2009
itoa(value, output_buffer, base);

Если вы используете 2 для базы, вы получите двоичную версию в строке.

Обратите внимание, что я только отвечаю " Есть ли лучший способ ", а не какой-либо другой компонент вопроса.

РЕДАКТИРОВАТЬ: Кроме того, вы можете посмотреть на популярные реализации в Итоа, чтобы увидеть, как они сделали многоосновное преобразование, не требуя математических функций (из -lm). Я знаю, что многие из тех, кого я видел в Италии, довольно маленькие, изящные и все еще довольно мощные.

3 голосов
/ 10 августа 2009

Вы можете использовать glibc для регистрации пользовательского спецификатора преобразования printf:

Настройка printf

Тогда вы можете сделать:

printf("Binary Representation:  %b\n", num);

Это было бы более гибко, чем вызов printf () внутри вашей функции.

Вам все равно нужно указать функцию для выполнения преобразования; но тогда вы можете использовать printf во всем коде.

Ответ Бастьена Леонара имеет более идиоматическую функцию для преобразования: используется побитовое и вместо модуля 2, сдвиг битов вместо деления и троичный оператор вместо if else.

Вот связанный вопрос:

есть-есть-а-Printf-конвертер-печать-в-двоичный формат

2 голосов
/ 10 августа 2009

Хорошо, еще одно возможное решение с использованием справочной таблицы:

#include <stdio.h>

#undef BIGENDIAN

#ifdef BIGENDIAN
enum { TSIZE = sizeof(int), INIT = 0, END = TSIZE };
#define op(x) ++(x)
#define cond(x) ((x) < END)

#else
enum { TSIZE = sizeof(int), INIT = TSIZE - 1, END = -1 };
#define op(x) --(x)
#define cond(x) ((x) > END)

#endif

static char *binstr[] = {
  "0000", // 0x0
  "0001", // 0x1
  "0010", // 0x2
  "0011", // 0x3
  "0100", // 0x4
  "0101", // 0x5
  "0110", // 0x6
  "0111", // 0x7
  "1000", // 0x8
  "1001", // 0x9
  "1010", // 0xA
  "1011", // 0xB
  "1100", // 0xC
  "1101", // 0xD
  "1110", // 0xE
  "1111", // 0xF
};


int main(void)
{
  int num, i;
  unsigned char *hex;

  hex = ((unsigned char *) &num);
  while(fscanf(stdin, "%i", &num) != EOF)
  {
    for(i = INIT; cond(i); op(i))
      printf("%s%s", binstr[hex[i]>>4], binstr[hex[i]&0xF]);
    printf("\n");
  }

  return 0;
}

PD: Я проверяю только с организацией порядка байтов.

2 голосов
/ 10 августа 2009

Вы конвертируете в (char *), потому что хотите использовать битовую смену? Если да, знаете ли вы об операторе сдвига битов?

short int n = 1;  //0x0001
n = n << 1;       //shift bits 1 place to the left
                  //n is now 2; 0x0010

Просто для хихиканья, вот процедура печати двоичного представления с использованием оператора сдвига битов:

void printbitssimple(int n) {
    unsigned int i;
    i = 1<<(sizeof(n) * 8 - 1);

    while (i > 0) {
        if (n & i)
            printf("1");
        else
            printf("0");
        i >>= 1;
    }
}
1 голос
/ 10 августа 2009

Это одна из величайших трагедий арабской нотации, в которой мы ставим самую значимую цифру первой. Почти все вычисления проще, когда мы начинаем с наименее значащей цифры:

void fprint_binary(FILE *fp, unsigned long n) {
  char digits[8*sizeof(n)+1];
  char *p = digits+sizeof(digits)-1;
  *p = '\0';
  unsigned long mask;
  for (mask = 1; mask; mask <<= 1)
    *--p = mask & n ? '1' : '0';
  while (*p == '0')
    p++;
  fprintf(fp, "%s", *p ? p : "0");
}

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

1 голос
/ 10 августа 2009

Еще один вариант. Это просто перебирает все биты от старшего к младшему и проверяет, установлены ли они.

void binary_repr(unsigned long input)
{
    int i = sizeof(input) * 8 - 1;
    for (; i >= 0; --i) {
        putchar((input & (1 << i)) == 0 ? '0' : '1');
    }

    putchar('\n');
}

Это не делает ничего, что еще не было предложено другими людьми здесь. Это просто метод, который мне легче запомнить.

...