Шестнадцатеричная строка со знаком для функции long int

Мне нужна функция для преобразования 32-битной или 24-битной шестнадцатеричной строки со знаком (в двух дополнениях) в длинное int. Должен работать как на 32-битных, так и на 64-битных машинах (независимо от размера long int) и работать независимо от того, является ли машина машиной с дополнением до двух или нет.

РЕШЕНИЕ:

long int hex2li (char hexStr[], int signedHex)
{
   int bits = strlen (hexStr) * 4;

   char *pEnd;
   long long int result = strtoll (hexStr, &pEnd, 16);

   if (pEnd[0] == '\0')
   {
      if (signedHex)
      {
         if (result >= (1LL << (bits - 1))) result -= (1LL << bits);
      }

      return (long int) result;
   }

   return LONG_MIN;
}

person Cheetah    schedule 06.05.2010    source источник
comment
Это неправильно - шестнадцатеричные строки, начинающиеся с 8, 9, A, B, C, D и E, тоже должны быть отрицательными.   -  person caf    schedule 07.05.2010
comment
Вы не сможете получить одну функцию для выполнения обоих типов строк, поскольку FFFFFF означает разные вещи, такие как 24-битное шестнадцатеричное число со знаком и 32-битное шестнадцатеричное число со знаком. Кроме того, действительно ли необходимо работать над машиной, не дополняющей два?   -  person CB Bailey    schedule 08.05.2010
comment
Похоже, вы работаете на машине, где long - 32 бита. FFFFFFFF больше максимального значения, представимого в 32-битном значении со знаком, поэтому 2 ^ 31 -1 является наибольшим представимым значением, поэтому оно используется вместо него.   -  person CB Bailey    schedule 08.05.2010
comment
Я подумал, что вы можете использовать длину строки, чтобы получить количество байтов, чтобы вы знали, следует ли интерпретировать ее как 24 или 32-битную строку. В основном мне нужна функция, которая отображает строки FFFFFF и FFFFFFFF на -1.   -  person Cheetah    schedule 09.05.2010
comment
Что ж, вы можете использовать разницу между pEnd и hexStr, чтобы увидеть, сколько цифр было преобразовано, но это будет весьма необычный интерфейс. Вы действительно хотите рассматривать 00FFFF и FFFF как разные числа? От чего в спецификации говорится, что вы получаете ваш вклад?   -  person CB Bailey    schedule 09.05.2010
comment
Да, 00FFFF и FFFF - разные числа для этой конкретной проблемы. Входная строка определенно является допустимой шестнадцатеричной строкой. Так что я мог бы просто использовать следующее: int bits = strlen (hexStr) * 4;. У меня есть решение из вашего сообщения ниже: if (result >= (1LL << (bits - 1))) result -= (1LL << bits);   -  person Cheetah    schedule 09.05.2010


Ответы (5)


Для 24-битной строки:

Когда вы разбираете шестнадцатеричную строку, стандартная функция strtol будет читать ее как беззнаковое значение в диапазоне 0 -> 2^24 - 1.

Диапазон 0 -> 2^23 - 1 правильный, но диапазон 2^23 -> 2^24 - 1 необходимо сопоставить с -2^23 -> -1, что является простым вычитанием, которое можно выполнить следующим образом.

if (result >= (1L << 23))
    result -= (1L << 24);

Чтобы преобразовать 32-битную строку с использованием того же метода, вы должны использовать промежуточный тип, который может представлять полное 32-битное целое число без знака в типе со знаком для выполнения вычитания. long long int гарантированно будет 64-битным, так что вы можете его использовать.

E.g.

long int ParseHexStr(const char *in, int bits)
{
    char* endptr;
    long long int result;

    result = strtoll(in, &endptr, 16);

    /*
    ** TODO - error checking, e.g. check endptr != in
    **  Also check for range errors, signalled by LLONG_MIN
    **  LLONG_MAX and a errno == ERANGE.
    */

    if (result >= (1LL << (bits - 1))
        result -= (1LL << bits);

    return result;
}
person CB Bailey    schedule 06.05.2010
comment
+1 Красиво и просто, учитывает вероятность того, что long больше 32 бит. - person Niall C.; 06.05.2010
comment
Это также должно работать, даже если ваша архитектура на самом деле не использует два дополнения, чего не будет в подходах с битовым тиддлингом; хотя я признаю, что это немного любопытно, учитывая преобладание двух дополнительных архитектур. - person CB Bailey; 06.05.2010
comment
Как мне получить подобное решение для 32-битных строк? - person Cheetah; 08.05.2010
comment
Это зависит. Если long int длиннее 32 бит, вы можете использовать точно такой же метод. Если long int - это ровно 32 бита, и вы работаете на машине с дополнением до двух, вам не нужно ничего делать. strtol даст правильный результат. - person CB Bailey; 08.05.2010
comment
Есть ли способ отредактировать функцию, чтобы охватить все базы. Я знаю, что вы сказали 2 комментария выше, что он работает независимо от того, является ли архитектура машины дополнением двух или нет, но как насчет 32-битных и 64-битных машин. Я попытался изменить его с long на int, но FF FF FF FF по-прежнему не дает -1. - person Cheetah; 08.05.2010
comment
@Ben: Я думаю, вам нужно либо обновить свой вопрос, либо опубликовать новый, потому что проблема, которую вы пытаетесь решить, отличается от вопроса, который вы опубликовали. if (result >= (1L << 31)) reuslt -= (1L << 32); должен работать, если long больше 32 бит, в противном случае strtol должен работать без настройки, если вы не используете машину с дополнением до двух. - person CB Bailey; 08.05.2010
comment
Извините, да, это сбивает с толку. Отредактировали вопрос. - person Cheetah; 08.05.2010
comment
@Ben: Я только что понял, что в последнем комментарии я не думал прямо. Я имел в виду, что на машине, где long - это 32 бита, вы можете использовать strtoul (не strtol), просто приводя результат. - person CB Bailey; 09.05.2010
comment
Использую долго долго работает. Спасибо. Я так понимаю, это гарантированно будет 64 бита или больше? - person Cheetah; 09.05.2010

У нас есть макрос SIGN_EXTEND, который выглядит так:

#define SIGN_EXTEND(X, SignBit, Type) \
    (((Type) ((X) << (8 * sizeof(Type) - (SignBit) - 1))) >> \
     (8 * sizeof(Type) - (SignBit) - 1))

Он полагается на оператор >> 1, заполняющий ввод, когда установлен знаковый бит. Используйте это как:

SIGN_EXTEND(0x89abcd, 23, int32_t);

Для вашей проблемы вы можете использовать:

long int hex2li (char string[])
{
    char *pEnd;
    long int result = SIGN_EXTEND(strtol (string, &pEnd, 16), 23, long int);

    if(pEnd[0] == '\0')
        return result;
    return LONG_MIN;
}
person Aidan Cully    schedule 06.05.2010
comment
+1 за общий МАКРОС, который правильно называет проблему. Вы могли бы улучшить, используя CHAR_BIT вместо 8, но это только придирки. - person Patrick Schlüter; 06.05.2010

Это сравнение неверно: if (toupper (string[0]) == 'F')

Вам нужно будет подписать-extension для любого значения с установленным MSB, например:

if(strchr("89ABCDEF", toupper(string[0])) != NULL)

person Niall C.    schedule 06.05.2010

Есть ли причина, по которой вы не можете использовать strtol с системой счисления 16?

person Flad    schedule 06.05.2010
comment
Он уже есть, но это не полное решение, так как long больше 24 бит и у него шестизначное шестнадцатеричное представление. - person CB Bailey; 06.05.2010

  if (toupper (string[0]) == 'F')
  {
     return (result | 0xFF000000);
  }

это даст номер с правильным знаком.

  if (toupper (string[0]) == 'F')
  {
     return ( ~(result | 0xFF000000) + 1);
  }

это всегда будет давать положительный результат

person Andrey    schedule 06.05.2010
comment
Как насчет того, чтобы первая цифра была «8», «9», «а»,… «е»? Он все равно должен быть отрицательным. - person Aidan Cully; 06.05.2010
comment
Не только "F" - необходимо проверить другие значения с набором MSB if (result & 0x00800000) - person David Gelhar; 06.05.2010
comment
@Aidan Cully: 24-битная шестнадцатеричная строка - person Andrey; 06.05.2010
comment
@ Дэвид Гелхар: я не согласен. например, вход был FFFFFF, что означает -1. при преобразовании в int он станет 00FFFFFF. Нам нужно сделать его подписанным, поэтому набираем старшие цифры | 0xFF000000, поэтому он становится действительным -1: 0xFFFFFFFF. - person Andrey; 06.05.2010
comment
рассмотрим 16-битное число со знаком 0x8000. Его значение в виде десятичного знака со знаком -32768. Шестнадцатеричная строка, представляющая это, - "8000". Первая цифра - не F, но если бы вы расширили ее до 32 битов, результат должен быть 0xFFFF8000, чтобы продолжить представление значения -32768. - person Aidan Cully; 06.05.2010