【问题标题】:Signed Hexadecimal string to long int function带符号的十六进制字符串到 long int 函数
【发布时间】:2010-05-06 14:34:45
【问题描述】:

我需要一个函数将 32 位或 24 位有符号(二进制补码)十六进制字符串转换为长整数。需要在 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;
}

【问题讨论】:

  • 这是不对的 - 以8、9、A、B、C、D 和 E 开头的十六进制字符串也应该都是负数。
  • 您将无法获得一个函数来处理两种类型的字符串,因为 FFFFFF 意味着不同的事物,如 24 位有符号十六进制数和 32 位有符号十六进制数。另外,在非二进制补码机器上工作是真正的要求吗?
  • 看起来您正在使用long 为 32 位的机器。 FFFFFFFF 大于 32 位有符号值中可表示的最大值,因此,2^31 -1 是可表示的最大值,因此改为使用。
  • 我认为您可以使用字符串的长度来获取字节数,以便您知道是将其解释为 24 位还是 32 位字符串。基本上我想要一个将字符串FFFFFF 和FFFFFFFF 映射到-1 的函数。
  • 嗯,您可以使用pEnd 和hexStr 之间的差异来查看转换了多少位,但这将是一个非常不寻常的界面。您真的要将“00FFFF”和“FFFF”视为不同的数字吗?规范说您从什么获得输入?

标签: c string hex twos-complement


【解决方案1】:

对于 24 位字符串:

当您解析十六进制字符串时,标准strtol 函数会将其读取为0 -&gt; 2^24 - 1 范围内的无符号值。

0 -&gt; 2^23 - 1 的范围是正确的,但是2^23 -&gt; 2^24 - 1 的范围需要映射到-2^23 -&gt; -1,这是一个简单的减法,可以如下执行。

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

要使用相同的技术转换 32 位字符串,您必须使用中间类型,该类型可以在有符号类型中表示完整的 32 位无符号整数,以执行减法。 long long int 保证为 64 位,因此您可以使用它。

例如

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;
}

【讨论】:

  • +1 漂亮而简单,并处理了 long 大于 32 位的可能性。
  • 即使你的架构实际上没有使用二进制补码,它也应该可以工作,而比特旋转方法则不会;虽然我承认这有点奇怪,因为二进制补码架构占主导地位。
  • 如何获得类似的 32 位字符串解决方案?
  • 这取决于。如果long int 长于 32 位,那么您可以使用完全相同的技术。如果long int 正好是 32 位,并且您使用的是二进制补码机器,那么您无需执行任何操作。 strtol 会给出正确的结果。
  • 有没有办法编辑函数以覆盖所有基础。我知道您在上面说过 2 cmets,无论机器架构是否是二进制补码,它都可以工作,但是 32 位和 64 位机器呢?我尝试将其从 long 更改为 int,但“FF FF FF FF”仍然不会产生 -1。
【解决方案2】:

我们有一个SIGN_EXTEND 宏,看起来像:

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

它依赖于&gt;&gt; 运算符 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;
}

【讨论】:

  • +1 用于正确命名问题的通用宏。您可以通过使用 CHAR_BIT 而不是 8 来改进,但这只是吹毛求疵。
【解决方案3】:

这个比较是错误的:if (toupper (string[0]) == 'F')

您需要使用 MSB 集对任何值进行符号扩展,例如:

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

【讨论】:

    【解决方案4】:

    为什么不能使用带有基数 16 的 strtol 有什么原因吗?

    【讨论】:

    • 他已经是了,但这不是一个完整的解决方案,因为 long 大于 24 位,并且他有 6 位十六进制表示。
    【解决方案5】:
      if (toupper (string[0]) == 'F')
      {
         return (result | 0xFF000000);
      }
    

    这将产生具有正确符号的数字。

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

    这总是会产生积极的结果

    【讨论】:

    • 第一个数字是 '8', '9', 'a', .. 'e' 呢?它应该仍然是负数。
    • 不仅仅是 'F' - 需要使用 MSB 集检查其他值 if (result &amp; 0x00800000)
    • @Aidan Cully:“24 位十六进制字符串”
    • @David Gelhar:我不同意。例如输入是 FFFFFF,表示 -1。当转换为 int 时,它将变为 00FFFFFF。我们需要让它签名,所以我们填写更高的数字 | 0xFF000000 所以变成实数 -1: 0xFFFFFFFF.
    • 考虑 16 位有符号数 0x8000。它的带符号十进制值为-32768。表示它的十六进制字符串是"8000"。第一个数字是not F,但如果要将其符号扩展为 32 位,则结果应为 0xFFFF8000,以便继续表示值 -32768。
    猜你喜欢
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多