Преобразование шестнадцатеричного в десятичное [упражнение K & R] - PullRequest
6 голосов
/ 25 апреля 2009

Я изучаю C и не могу понять одно из упражнений K & R, список:

Упражнение 2-3, напишите функцию htoi(s), который преобразует строку шестнадцатеричные цифры (включая необязательно 0x или 0X) в эквивалент целочисленное значение. Допустимые цифры от 0 до 9, a до f и A через F.

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

Может ли кто-нибудь дать мне несколько советов о том, как лучше всего это понять, я не ищу, чтобы кто-то держал меня за руку, а вместо этого направил меня к средствам правильного понимания, чтобы я мог написать это в самой изящной форме, насколько это возможно а не с printf("%x", skill);

Ответы [ 8 ]

12 голосов
/ 25 апреля 2009

Рекурсия не нужна. Вам просто нужно выполнить цикл в обратном направлении по всей строке (т. Е. Начиная с столбца единиц), суммируя время преобразования одной цифры в множитель радикального положения. Это псевдокод, который не обрабатывает необязательный префикс 0x (и не проверяет возможность переполнения):

long total = 0;
long multiplier = 1;
for (int i = string.length - 1; i >= 0 i--)
{
   digit = ConvertSingleHexDigittoInt(string[i]);
   total += digit * multiplier;
   multiplier *= 16;
}

Я оставил вам простую реализацию ConvertSingleHexDigittoInt ():)

5 голосов
/ 25 апреля 2009

Митч правильно понял основную идею, но давайте рассмотрим ее чуть подробнее.

Шестнадцатеричное число - это просто основание 16, что означает, что цифры (справа налево) имеют значения как

цифра и время; 16 0 (т. Е. 1)
цифра и время; 16 1 (т.е. 16)
цифра и время; 16 2 (256)

и так далее. Так, 0xE равно 14, например.

Вам понадобится цикл, начинающийся в правом конце строки. Допустим, строка s, длина (s) - длина строки. В псевдокоде, вы хотите

value = 0
r = 1   // ask yourself "what values does r take as this proceeds?"
for i from length(s)-1 to 0   // Ask yourself "why length(s)-1?"
   value = value + (digitval(s[i])*r)
   // get ready for the next digit
   r = r * 16

digitval(char c) должна быть функцией, которая переводит checract в "0123456789ABCDEF" в значения между 0 и 15 (включительно). Я оставлю это как упражнение с одной подсказкой: "массивы".

будьте осторожны с одной дополнительной проблемой; поскольку у вас может быть начальный «0» или «0x», вам необходимо убедиться, что вы обрабатываете эти случаи.

4 голосов
/ 25 апреля 2009

Обработка строки слева направо проще и, возможно, более удобочитаема для тех, кто знаком с математикой. Стратегия понимает, что, например, 1234 = (((1 x 10) + 2) x 10 + 3) x 10 + 4

Другими словами, обрабатывая каждую цифру слева направо, умножьте предыдущую сумму на основание, фактически «сдвинув ее влево» на одну позицию, затем добавьте новую цифру.

long decFromHexStr(const char *hexStr)
{
    int i;
    long decResult = 0;  // Decimal result

    for (i=0;  i < strlen(hexStr);  ++i)
    {
        decResult = 16 * decResult + decFromHexChar(hexStr[i]);
    }
    return decResult;
}

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

long decFromHexStr(const char *pHex)
{
    long decResult = 0;

    while (*pHex != '\0')
    {
        decResult = 16 * decResult + decFromHexChar(*pHex++);
    }
    return decResult;
}

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

Веселись!

2 голосов
/ 25 апреля 2009

Что на самом деле означает шестнадцатеричное число? Давайте возьмем 15FA . Это значит

1 * 16^3 + 5 * 16^2 + 15 * 16^1 + 10 * 16^0

Обратите внимание, что A представляет десять, B одиннадцать и т. Д. До F , что представляет пятнадцать. Также 16 ^ 0 равно 1.

Так что все, что нам нужно сделать, это вычислить значение вышеприведенного выражения! Самый простой способ, вероятно, сделать это в следующем порядке:

10 * 1
15 * 16
5  * 256   //256  = 16 * 16
1  * 4096  //4096 = 16 * 16 * 16

Это может продолжаться дальше, если есть другие цифры. Все, что вам действительно нужно, это цикл и несколько переменных.

Существует еще один способ сделать это, который объясняется разложением приведенного выше выражения следующим образом:

((1 * 16 + 5) * 16 + 15) * 16 + 10

Если хотите, попробуйте каждый из этих способов.

Более подробная информация:

В основном компьютеры используют базу 2 (также называемую двоичным) для всех своих чисел и расчетов. Даже строка «1A6DC0» кодируется с 1 и 0, которые в конечном итоге отображаются на экране в виде букв и цифр.

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

Например, когда вы делаете

x = (11 + y) * 6;

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

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

Например, 15FA будет расширяться следующим образом:

1 -> 0001
5 -> 0101
F -> 1111
A -> 1010
15FA -> 0001 0101 1111 1010

Обратите внимание, что это, как правило, не может быть сделано напрямую, и обычно включает в себя логические или битовые сдвиги (| и <<). Прикольные вещи.

1 голос
/ 25 апреля 2009

попробуй объяснить с моим грубым английским: (

Мой код (предположим, что все входные данные корректны. Избегайте защитного программирования)

#include <stdio.h>


enum { SZ = 11 };

unsigned int htoi(const char *s);


int main()
{

  char buff[SZ];  //Max 11 char: 0x XX XX XX XX '\0' (2 + 8 + 1)

  while(fscanf(stdin, "%s", buff) != EOF)
    printf("%X\n", htoi(buff) ); 

  return 0;
}


unsigned int htoi(const char *s)
{
  unsigned int i, r = 0;

  for(i = (s[1] == 'x') ? 2 : 0; s[i] != '\0'; i++)
    r = ( r << 4 ) +  ( (s[i] > '9') ? 0x9 : 0x0 ) + ( s[i] & 0xF );

  return r;
}

Хорошо, прежде всего, присвойте r = 0. Затем, когда мы запускаем for-bucle, мы передаем значение init индексной переменной i. Мы должны проверить, имеет ли строка формат 0x или нет. Нам нужно только проверить позицию 1, чтобы узнать, обрабатываем ли мы входную строку в формате 0x или без него.

Теперь у нас есть индекс, указывающий на первый правильный символ! Для каждой итерации мы смещаем 4 бита влево. Мы получаем 4 нуля. Идеальный пробел, чтобы добавить новую шестнадцатеричную цифру! Пример:

Input: 0xBE1234

Is s[1] == 'x' ? true then i = 2;
r = 0;

iter 1: r = 0x0; r = 0x0; r = 0xB;
iter 2: r = 0xB; r = 0xB0; r = 0xBE;
iter 3: r = 0xBE; r = 0xBE0; r = 0xBE1;
iter 4: r = 0xBE1; r = 0xBE10; r = 0xBE12;
iter 5: r = 0xBE12; r = 0xBE120; r = 0xBE123;
iter 6: r = 0xBE123; r = 0xBE1230; r = 0xBE1234

Может быть, это немного сложнее:

 r = ( r << 4 ) + ( (s[i] > '9') ? 0x9 : 0x0 ) + ( s[i] & 0xF );

Прежде всего, мы смещаем 4 бита, так же, как умножение на 16, но более эффективно. Затем мы смотрим, если у нас есть символ ASCII больше, чем «9». Если это правда, мы работаем с A, B, C, D, E, F или a, b, c, d, e, f. Помните, мы предполагаем, что у нас есть правильный ввод. Хорошо, теперь взглянем на таблицу ASCII:

A = 0100 0001  -  a = 0110 0001
...
F = 0100 0110  -  f = 0110 0110

но мы хотим что-то вроде этого:

A = 0000 1010  -  a = 0000 1010
...
F = 0000 1111  -  f = 0000 1111

Как мы это делаем? После смещения очищаем 4 старших бита с маской s [i] & 0xF:

s[2] == 'B' == 0100 0010
s[2] & 0xF == 0000 0010

и добавить 9 для адаптации к целочисленному значению (только в том случае, если s [i] в ​​{'A' ... 'F', 'a' ... 'f'})

s[2] & 0xF + 0x9 = 0000 0010 + 0000 1001 = 0000 1011 (0xB)

Наконец, мы добавляем к смещенному значению r и присваиваем значение r. Последовательность выполнения для второй итерации (с [3]):

r == 0xB, s[3] == 'E' == 0100 0101 (start iter 2)
(r << 4) == 0xB0, s[3] == 'E' == 0100 0101 (displacement r << 4 )
(r << 4) == 0xB0, (s[3] & 0xF + 0x9) == 0000 1110 == 0xE (clear most significant bits of s[3] and add 0x9)
r = (r << 4) + ( s[3] & 0xF + 0x9 ) == 0xBE == 1011 1110 (add all and assign to r)

Что произойдет, если у нас будет такой числовой символ, как s [4]?

s[4] == '1' == 0011 0001
s[4] & 0xF == 0000 0001

Смещение r на четыре позиции, добавление 0 (ничего), добавление результата логической операции s [i] & 0xF и, наконец, присвоение r.

r == 0xBE, s[4] == '1' == 0011 0001 (start iter 3)
(r << 4) == 0xBE0, s[4] == '1' == 0011 0001 (displacement r << 4 )
(r << 4) == 0xBE0, (s[4] & 0xF + 0x0) == 0000 0001 (clear most significant bits of s[4] and add 0)
r = (r << 4) + s[4] & 0xF == 0xBE1 == 1011 1110 0001 (add all and assign)

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

PD: Я обещаю улучшить мой английский для объяснения лучше, извините.

1 голос
/ 25 апреля 2009

Я, вероятно, не делаю большой вклад, есть хорошие ответы выше. Но я попробую.

Как и другие до меня, я оставляю вам некоторые функции для реализации.

int htoi(const char* x)
{

        unsigned int current_position;/*current position is to be defined*/
        int prefixed=0;                                                         
        int dec=0;
        char* y = x;

        if (x && x+1 && (*(x+1)=='x' || *(x+1)=='X')){  /*Is 0x or 0X prefix present?*/
                prefixed= PREFIXED;             
        }

        if (prefixed) y+=2; /*Jumps over 0x or 0X*/     


        while (*y){
                /*getPos(const char*) and singleHexToDec(const char*,unsigned int) functions to be implemented*/
                current_position=getPos(y);
                dec+=singleHexToDec(y,current_position); 
        }
        return dec;
}
1 голос
/ 25 апреля 2009

Обычный подход конвертирует слева направо. Аккумулятор устанавливается в ноль в начале и умножается на 16 перед добавлением эквивалентного значения каждой новой цифры в цикл.

Для функции htoi(), которая ожидает шестнадцатеричные цифры с необязательным начальным значением 0x, начните с пропуска этих символов, если они присутствуют. Прямой контроль значений s[0] и s[1], вероятно, является наиболее ясным подходом.

Если вы знаете, что цифры находятся в ASCII, то вы можете использовать выражения типа s[i] - '0' и s[i] - 'A' + 10 для преобразования i-й цифры в ее целочисленное значение.

Вы, вероятно, хотите сложить все это в одно дело для здравомыслия.

Редактировать: Изменено *s на s[i] для соответствия с наблюдением, что указатели из будущего с точки зрения этого упражнения.

Обратите внимание, что есть несколько других способов преобразования отдельных цифр в значения. Например, вы можете найти их в векторе из всех цифр (что-то вроде strchr("0123456789ABCDEF",s[i])), построить одну таблицу поиска, индексированную кодом символов со значением каждой цифры в каждой позиции (digitvalue[s[i]] после того, как int digitvalue[256] было соответственно инициализировано), используйте оператор switch (s[i]) с меткой case для каждой возможной цифры, как предлагается в другом ответе, или используйте проверки диапазона и арифметику, как я предлагал выше. Надо подумать, что выбрать и почему. Обратите внимание, что это не может быть очевидным выбором, и лучший ответ может быть другим, если ASCII не является вашим выбором символов.

0 голосов
/ 25 апреля 2009

Вчера я написал такую ​​функцию. Вы можете увидеть мой код ниже.

/* Converting a hex string to integer, assuming the heading 
   0x or 0X has already been removed and pch is not NULL */
int hex_str_to_int(const char* pch) {

    int value = 0;
    int digit = 0;

    for (; *pch; ++pch) {

        if (*pch >= '0' && *pch <= '9') {
            digit = (*pch - '0');
        } else if (*pch >= 'A' && *pch <= 'F') {
            digit = (*pch - 'A' + 10);
        } else if (*pch >= 'a' && *pch <= 'f') {
            digit = (*pch - 'a' + 10);
        } else {
            break;
        }

        // Check for integer overflow
        if ((value *= 16) < 0 || (value += digit) < 0) {
            return INT_MAX;
        }
    }

    return value;
}

Вот код тестирования:

int main(void) {

    printf("%d %d\n", hex_str_to_int("0"), 0x0);
    printf("%d %d\n", hex_str_to_int("A"), 0xA);
    printf("%d %d\n", hex_str_to_int("10"), 0x10);
    printf("%d %d\n", hex_str_to_int("A1"), 0xA1);
    printf("%d %d\n", hex_str_to_int("AB"), 0xAB);
    printf("%d %d\n", hex_str_to_int("100"), 0x100);
    printf("%d %d\n", hex_str_to_int("1A2"), 0x1A2);
    printf("%d %d\n", hex_str_to_int("10A"), 0x10A);
    printf("%d %d\n", hex_str_to_int("7FFFFFF"), 0x7FFFFFF);
    printf("%d %d\n", hex_str_to_int("7FFFFFF1"), 0x7FFFFFF1);
    printf("%d %d\n", hex_str_to_int("7FFFFFF2"), 0x7FFFFFF2);
    printf("%d %d\n", hex_str_to_int("7FFFFFFE"), 0x7FFFFFFE);
    printf("%d %d\n", hex_str_to_int("7FFFFFFF"), 0x7FFFFFFF);
    printf("%d %d\n", hex_str_to_int("80000000"), 0x7FFFFFFF + 1);
    printf("%d %d\n", hex_str_to_int("80000001"), 0x7FFFFFFF + 2);

    printf("%d %d\n", hex_str_to_int("10AX"), 0x10A);   
    printf("%d %d\n", hex_str_to_int("203!"), 0x203);

    return 0;
}

Выводит следующие значения:

0 0
10 10
16 16
161 161
171 171
256 256
418 418
266 266
134217727 134217727
2147483633 2147483633
2147483634 2147483634
2147483646 2147483646
2147483647 2147483647
2147483647 -2147483648
2147483647 -2147483647
266 266
515 515
...