views:

280

answers:

5

I need a function to convert a 32bit or 24bit signed (in two's complement) hexadecimal string into a long int. Needs to work on both 32bit and 64bit machines (regardless of the size of long int) and work regardless of whether the machine is a two's complement machine or not.

SOLUTION:

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;
}
A: 
  if (toupper (string[0]) == 'F')
  {
     return (result | 0xFF000000);
  }

this will produce number with correct sign.

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

this will always produce positive result

Andrey
What about for the first digit being '8', '9', 'a', .. 'e'? It should still be negative.
Aidan Cully
David Gelhar
@Aidan Cully: "24bit Hexadecimal string"
Andrey
@David Gelhar: i disagree. for example input was FFFFFF which mean -1. when converted to int it will become 00FFFFFF. We need to make it signed, so we fill higher digits | 0xFF000000 so it becomes real -1: 0xFFFFFFFF.
Andrey
@Andrey: consider the 16-bit signed number `0x8000`. Its value as a signed decimal is `-32768`. The hex string that represents it is `"8000"`. The first digit is _not_ `F`, but if you were to sign-extend it to 32-bits, the result should be `0xFFFF8000`, in order to continue representing the value `-32768`.
Aidan Cully
+1  A: 

We have a SIGN_EXTEND macro, that looks like:

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

It relies on the >> operator 1-filling the input when the sign bit is set. Use it like:

SIGN_EXTEND(0x89abcd, 23, int32_t);

For your problem, you could use:

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;
}
Aidan Cully
+1 for a generic MACRO that names the problem correctly. You could improve by using CHAR_BIT instead of 8, but that's only nitpicking.
tristopia
+1  A: 

This comparison is wrong: if (toupper (string[0]) == 'F')

You'll need to sign-extend for any value with the MSB set, so something like:

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

Niall C.
+5  A: 

For a 24-bit string:

When you parse the hex string, the standard strtol function will read it as an unsigned value in the range 0 -> 2^24 - 1.

The range 0 -> 2^23 - 1 is correct, but the range 2^23 -> 2^24 - 1 needs to be mapped to -2^23 -> -1 which is a simple subtraction which can be performed as follows.

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

To convert a 32-bit string using the same technique you have to use an intermediate type that can represent a full 32-bit unsigned integer in a signed type for performing the subtraction. A long long int is guaranteed to be 64-bits so you can use this.

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;
}
Charles Bailey
+1 Nice and simple, and takes care of the possibility that a long is bigger than 32 bits.
Niall C.
It should also work even if your architecture isn't actually using two's complement which bit twiddling approaches won't; although I admit this is a bit of a curiosity given the predominance of two's complement architectures.
Charles Bailey
+1 for working on non 2's complement architectures.
Aidan Cully
How would I get a similar solution for 32 bit strings?
Ben
That depends. If `long int` is longer that 32 bits then you can use exactly the same technique. If `long int` is exactly 32 bits and you're on a two's complement machine then you don't have to do anything. `strtol` will give the correct result.
Charles Bailey
Is there a way to edit the function to cover all bases. I know you said 2 comments above that it works regardless of the whether the machines architecture is two's complement or not, but what about 32bit and 64bit machines.I tried changing it from long to int but "FF FF FF FF" still doesn't produce -1.
Ben
@Ben: I think you either need to update your question or post a new one because whatever problem you are trying to solve is different from the question that you have posted. `if (result >= (1L << 31)) reuslt -= (1L << 32);` should work if `long` is more than 32 bits, otherwise `strtol` should work without adjustment unless you're not using a two's complement machine.
Charles Bailey
Sorry, yeah it's getting confusing. Have edited the question.
Ben
@Ben: I've just realised that I wasn't thinking straight in the last comment. What I meant was that on a machine where a long is 32-bits you can use `strtoul` (not `strtol`), just casting the result.
Charles Bailey
Using a long long works. Thank you. I take it that this is guaranteed to be 64bits or more?
Ben
+1  A: 

Is there a reason why you cannot use strtol with radix 16?

Flad
He already is, but this isn't a complete solution as a `long` is larger than 24 bits and he has a 6-digit hex representation.
Charles Bailey