简体   繁体   中英

Convert single Character (hex number) to Integer in C

So for an assignment I have to convert a character ( 0 - F ) to an integer ( 0 - 15 ), 0 - 9 works fine, but if any letter is given, it prints a random number: For C for instance, it gives 19 , for D is returns 20 .

This is my method:

int char2int(char digit) {
    int i = 0;

    if (digit == 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9)
        i = digit - '0';
    else
    if (digit == 'A' || 'B' || 'C' || 'D' || 'E' || 'F')
        i = digit - '9';
    else
        i = -1;

    return i;
}

At first my if statements were like this:

if (digit => 0 && =< 9)

if (digit => A && =< F)

But that gave a number of errors. You can tell I don't know C very well. My current If statement works but I'm sure it's unnecessarily long.

if (digit == 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9)

This is not how conditional expressions work in C.

You either need to compare digit against each of the numbers individually

if (digit == '0' || digit == '1' || digit == '2' ...

or do it the clever way:

if(digit >= '0' && digit <= '9')
                         ^^ not =<

Notice that I put ' around the numbers because you want to compare the digit with the letter 0 and not the number (which is not the same see here for all the ASCII character values).

You were on the right path when you started, but wandered off a bit. try this

#include <ctype.h>
int char2int(char d) {
    if (!isxdigit(d)) {
        return -1;
        }
    if (isdigit(d)) {
        return d - '0';
        }
    return (tolower(d) - 'a') + 10;
    }

If you'd prefer an approach closer to your range testing, you could do it like this:

int char2int(char d) {
    if (d >= '0' && d <= '9') {
        return d - '0';
        }
    d = tolower(d);
    if (d >= 'a' && d <= 'f') {
        return (d - 'a') + 10;
        }
    return -1;
    }

Assuming ASCII the following converts from a character (0-9, af, AF) to the associated unsigned integer (0-15). Any other character will also be converted to... some random value in the 0-15 range. Garbage in, garbage out.

unsigned hexToUnsigned(char ch) {
    return ((ch | 432) * 239'217'992 & 0xffff'ffff) >> 28;
}

CPUs with 32-bit integers will generally be able to elide the 0xffff'ffff masking. On my machine the compiler turns this function into:

hexToUnsigned PROC
movsx   eax, cl
or      eax,1B0h
imul    eax, eax, 0E422D48h
shr     eax, 1ch
ret     0
hexToUnsigned ENDP

Another common way to do this has fewer apparent operations (just three), returns total garbage on invalid characters (which is probably okay), but also requires division (which takes it out of the top spot):

return ((ch | ('A' ^ 'a')) - '0') % 39;

To illustrate how compilers feel about division, they (at least on x64) change it into a multiply of the reciprocal to get the product and then one more multiply and subtract if you need the remainder:

hexToUnsigned PROC
; return ((ch | ('A' ^ 'a')) - '0') % 39;
movsx   r8d, cl
mov     eax, -770891565
or      r8d, 32
sub     r8d, 48
imul    r8d
add     edx, r8d
sar     edx, 5
mov     ecx, edx
shr     ecx, 31
add     edx, ecx
imul    ecx, edx, 39
sub     r8d, ecx
mov     eax, r8d
ret     0
hexToUnsigned ENDP

The return value is not random. Every ascii character is represented in the memory by a value. The value of each ascii character can be found in the Ascii Table .

The other responses tell you what you are doing wrong with the conditional expressions, but another mistake is that if the character is A, B, C, D, E or F you need to convert it to int like this i = ( digit - 'A' ) + 10 which means take the value of A, B, C, D, E or F subtract the min value which is A and add to that 10.

Moreover, you can see that if you don't need the exact value of a character you can do without the ascii table, using the property that letters are continuous.

If you are willing to make assumptions such as char are encoded as ASCII and 2's complement, the following is quite efficient.

This code is not meant for readability. Use other solutions if that is a concern. This is for tight encoding. With a given processor, it is about 10 instructions. Your results will vary.

Subtract 1. This shifts the char values down 1. In particular, AZ is now 64-89 and az in the range 96-121.

Test if a bit (64's place) is clear: in the range of '0' - '9'. If so, increment by 7 and mask to keep that bit (64's place) cleared.

Otherwise mask a bit to fold az into the AZ range.

Now '0' to '9' and 'A' to 'Z' are in a continues range. Just subtract 54. All unsigned char values other than 0-9 , AZ and az will have a value > 35. This is useful for any base use to base 36.

int Value(char ch) {
  if (!(--ch & 64)) {       // decrement, if ch in the '0' to '9' area ...
    ch = (ch + 7) & (~64);  // move 0-9 next to A-Z codes
  } else {
    ch &= ~32;
  }  
  ch -= 54;                 // -= 'A' - 10 - 1
  if ((unsigned char)ch > 15) { 
    ; // handle error
  }
  return (unsigned char)ch;
}

In redis

https://github.com/antirez/redis/blob/3.2.8/src/sds.c#L892

int hex_digit_to_int(char c) {
    switch(c) {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    case 'a': case 'A': return 10;
    case 'b': case 'B': return 11;
    case 'c': case 'C': return 12;
    case 'd': case 'D': return 13;
    case 'e': case 'E': return 14;
    case 'f': case 'F': return 15;
    default: return 0;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM