C is there a more elegant way to convert a char to an int or byte? - Page 1
Forum section: electronics / general

author: Jester
author_rank: Frequent Contributor
timestamp: May 04, 2022, 04:31:07 pm
content: I have a char array RxString filled with hex data as char, I need to convert a particular (character 0-F) to an int or byte, my solution works but seems pretty uglychar t;int dU;sprintf(t, "%c", RxString);int dU = (int) strtol(t,0, 16);I'm guessing there is a more elegant solution?

author: Ian.M
author_rank: Super Contributor
timestamp: May 04, 2022, 04:53:02 pm
content: A C char is an integral type, and on all except legacy 'big iron' that natively uses EBCDIC, will hold a character as its ASCII code.Try:Code:dU=RxString-'0'; // convert numeric digitsif(dU>9) dU-='A'-('0'+10); //fixup hex A-Fif(dU>15) dU-='a'-'A'; //fixup lowercase hexN.B. the above fails miserably if the input isn't a valid hex digit.Edit: Fixed the sign of the offset by 10 for digits A-F by inserting parentheses.

author: MarkF
author_rank: Super Contributor
timestamp: May 04, 2022, 05:33:42 pm
content: Code:char t;int dU;sscanf(t,"%x",&dU);

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 04, 2022, 05:34:53 pm
content: Ian's proposal also looks quite gnarly but it's quite easy to come up with some better algorithm to use the character constants to convert a single ascii character to a number.I would do it in a similar way though with a few character constants and a bit of addition and subtraction.It's upto you how robust you want to make it against faulty input.

author: ledtester
author_rank: Super Contributor
timestamp: May 04, 2022, 05:42:41 pm
content: To convert an entire string of hex digits to an int, see if you have the strtol() function available.Other related discussions from stackoverflow:https://stackoverflow.com/questions/10324/convert-a-hexadecimal-string-to-an-integer-efficiently-in-chttps://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int/58253380#58253380

author: Ian.M
author_rank: Super Contributor
timestamp: May 04, 2022, 05:48:15 pm
content: Any solution that requires printf() or scanf() family library functions is highly undesirable in most embedded contexts (*UNLESS* you are already unavoidably using them) due to the large amount of program memory they typically consume.However one approach that avoids both printf/scanf and explicit ASCII code hacking would be:Code:char t=" "; //initialize t with string terminatorint dU;t=RxString;dU=(int)strtol(t, NULL, 16);@ledtester: the O.P.s original code was using strtol()

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 04, 2022, 07:28:59 pm
content: Doing a small function for that will be many times more efficient than calling the std library.If only receiving uppercase chars:Code:uint8_t hex2int(char c){return ( (c<58) ? (c-48) : (c-55) ); // c<':' ? 0-9 : A-F}If lowercase must be considered:Code:uint8_t hex2int(char c){if(c<58)return(c-48); // 0-9if(c<71)return(c-55); // A-Freturn(c-87); // a-f}

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 04, 2022, 07:44:32 pm
content: Ian's version is hard to beat as far as efficiency and code compactness goes, for something case-agnostic, on most targets. But, it doesn't do input validation. You can play withhttps://godbolt.org/to see for yourself.For something very straightforward to read, and that does input validation (will return -1 if the character is not a valid hex), you can do this:Code:int HexCharToInt(char c){if ((c >= '0') && (c <= '9'))return c - '0';else if ((c >= 'A') && (c <= 'F'))return c - 'A' + 10;else if ((c >= 'a') && (c <= 'f'))return c - 'a' + 10;elsereturn -1;}While not as efficient, it's still just a few instructions and the difference will not matter much unless you issue millions of such conversions per second.An alternative, typically "branchless" approach similar to Ian's one would look like:Code:int HexCharToInt(char c){return (c - '0')*(c >= '0')- ('A' - '0' - 10)*(c >= 'A')- ('a' - 'A')*(c >= 'a');}But while pretty close to Ian's version, it's usually not quite as efficient and there's often still one branch (depending on target though.)But any of those works and is largely good enough, while being just a few bytes of code while pulling sprintf or even just strtol will pull KB's of library code, and be a lot less efficient.

author: free_electron
author_rank: Super Contributor
timestamp: May 04, 2022, 08:03:17 pm
content: since return immediately exits there is no need for if then else constructions. just let it fall throughand i would overlay an integer over the string.int x @if (x >=97) return x-97+10;if (x >=65) then return x-65+10;return x-48;

author: Ian.M
author_rank: Super Contributor
timestamp: May 04, 2022, 08:37:44 pm
content: Hmmm. Try:Code:n=(c-(c&64)/9)&15;which handles both upper and lower case ASCII hex digits. Sorry, no validation.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 04, 2022, 08:55:49 pm
content: Quote from: Ian.M on May 04, 2022, 08:37:44 pmAhah, nice one =)But while it's the absolute shortest in terms of code size so far, unfortunately the divide by 9 is an expensive operation on most targets, making it probably the least efficient (in terms of cycles) of all solutions we posted so far, except maybe for the strtol() one.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 04, 2022, 09:03:44 pm
content: Quote from: Ian.M on May 04, 2022, 08:37:44 pmBetter to use floats and make 32x oversampling to provide high fidelity chars

author: Ian.M
author_rank: Super Contributor
timestamp: May 04, 2022, 09:07:35 pm
content: @SiliconWizard,That rather depends on how good your compiler is at optimization. However I agree that the ternary operator form is a safer choice with most compilers on most MCUs, and only uses two more characters of sourcecode:Code:n=(c-(c&64?7:0))&15;On an Enhanced Midrange PIC12/16 or PIC18, with c in, and n out both in W, that *can* optimize to only three instructions:Code:btfsc WREG,6 ; skip if numericaddlw 0xF9 ; to fix up gap between '9' and 'A'andlw 0x0F ; we need only the low nibbleCredit should be given to Tamas Rudnai over at the PIClist for the method, though his code was more obscure, usingaddlw 9, the four bit two's compliment value of -7.

author: Jester
author_rank: Frequent Contributor
timestamp: May 04, 2022, 10:59:33 pm
content: Thanks to everyone,

author: MK14
author_rank: Super Contributor
timestamp: May 04, 2022, 11:00:03 pm
content: My Algorithm:'MK14's 14 char long algorithm'https://godbolt.org/z/xh69EbT4n(h>>6)*9+h&0xF // Short version, where h = the hex character value(hex>>6)*9+hex&0xF // Longer, implementation versionEliminating possiblyexpensivesystem calls, branches (slow on some architectures) and divides (except bit shifts).Validation code:Code:#include <stdio.h>#include <ctype.h>int HexToInt(char hex){//return toupper(hex)-64>0?toupper(hex)-55:toupper(hex)-48;return (hex>>6)*9+hex&0xF;}int main(void){for (char test = '0'; test<'G'; test++){printf("Hex %c gives an int of %d\n", test, HexToInt(test));if (test == '9') test = 'A' - 1; // Naughty hack!}}Results:Code:Hex 0 gives an int of 0Hex 1 gives an int of 1Hex 2 gives an int of 2Hex 3 gives an int of 3Hex 4 gives an int of 4Hex 5 gives an int of 5Hex 6 gives an int of 6Hex 7 gives an int of 7Hex 8 gives an int of 8Hex 9 gives an int of 9Hex A gives an int of 10Hex B gives an int of 11Hex C gives an int of 12Hex D gives an int of 13Hex E gives an int of 14Hex F gives an int of 15Originally designed for uppercase hex digits. May need reworking for lower-case.https://godbolt.org/z/xh69EbT4n

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 04, 2022, 11:19:10 pm
content: Quote from: MK14 on May 04, 2022, 11:00:03 pmIt does appear to be correct for lowercase as well! Nice one. Of course this is a trick, and not very "readable", but as long as it's properly commented, why not.I don't think one can do much better than this in terms of both code size and execution time. The multiply by 9 is very easily optimized as a shift and add by any compiler on targets for which it'll be faster than the multiply.

author: langwadt
author_rank: Super Contributor
timestamp: May 04, 2022, 11:24:58 pm
content: Quote from: MK14 on May 04, 2022, 11:00:03 pmafaict it shoudl also work with lower case because 'a'-'A' happens to be 32

author: MK14
author_rank: Super Contributor
timestamp: May 04, 2022, 11:42:46 pm
content: Quote from: SiliconWizard on May 04, 2022, 11:19:10 pmQuote from: langwadt on May 04, 2022, 11:24:58 pmYes, that is fortunate/good, that it works for BOTH cases.I think, I saw a very similar algorithm (NOT done by me), many years ago. So, although I'd forgotten the precise details, remembering that it could be done, in such a way, and a bit of thinking. Allowed me to make my own version.The important detail of what I remember seeing a long time ago. Was that the bit patterns can be easily manipulated, to change between Hex characters and integers. Because of the way ASCII values are constructed.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 12:11:37 am
content: The reason for it working for lowercase is that the difference between 'a' and 'A' in ASCII is 32, which means bit 5 set for lowercase letters, and clear for uppercase. Since the character code is shifted right by 6 positions in one place and masked with 0xF in the other, bit 5 simply doesn't matter. Pretty cool.

author: KrudyZ
author_rank: Frequent Contributor
timestamp: May 05, 2022, 04:17:08 am
content: The fastest execution time would be to use a look-up table.Not too terrible in "code size" either since it only has 256 entries.

author: Ian.M
author_rank: Super Contributor
timestamp: May 05, 2022, 05:01:46 am
content: I guess that depends on how many instructions your particular MCU takes to set up a table read vs how good it is at optimization. Its going to be hard to beat the bit-test ASCII 'bashing' method's potential three instruction minimum with all variables in registers on a processor capable of bit test and skip or branch.(see my reply #12)Meanwhile another variant to try that may be more efficient on your MCU is:Code:n=(9*!!(c&64)+c)&0xF;

author: m k
author_rank: Super Contributor
timestamp: May 05, 2022, 07:49:06 am
content: Is true defined as 1?(X & 0x0F) + ((X & 0x40) >> 3) + (X && 0x40)(X & 0x0F) + ((X & 0x40) >> 3) + ((X & 0x40) >> 6)

author: Ian.M
author_rank: Super Contributor
timestamp: May 05, 2022, 08:26:29 am
content: Quote from: m k on May 05, 2022, 07:49:06 amIt depends on what you mean by 'defined'. The canonical value oftruein both C and C++ is required by the standards to be numerically equal to 1, and any logical operator is required to return that when its result is true. Therefore!!(double negation) is a convenient way to flatten a numeric result to its canonical logical value. However if you look in the headers, you may find some rather idiosyncratic defines for true!

author: brucehoult
author_rank: Super Contributor
timestamp: May 05, 2022, 09:08:07 am
content: Quote from: m k on May 05, 2022, 07:49:06 amUnsure of the relevance to the code you posted, as nothing there produces a true/false value, except the (X && 0x40), which is always !!X.In C zero is false and everything else is true, but relational operators must produce 1/0.

author: m k
author_rank: Super Contributor
timestamp: May 05, 2022, 09:32:25 am
content: What are results for X && 0x40?Anyway,one successfully optimized would bemov ax,chrcmp ax,0x40jnc overadd ax,9over:and ax,0x0formov ax,chrmov bx,axand ax,0x0fand bx,0x40sar bx,3add ax,bxsar bx,3add ax,bxso logical operation is complicating things.

author: eugene
author_rank: Frequent Contributor
timestamp: May 05, 2022, 05:21:46 pm
content: Quote from: MK14 on May 04, 2022, 11:00:03 pmIt works for upper and lower case letters because they all have bit 6 set. More specificallyCode:hex & 0xFevaluates to 0 - 9 when hex == '0' - '9'evaluates to 1 - 6 when hex == 'A' - 'F' or 'a' - 'f'Checking bit 6 tells if the char is 'A' - 'F' or 'a' - 'f', soCode:(hex>>6)*9adds 9 or not depending on bit 6Is && an improvement over >>?Code:(hex && 0x40)*9 + hex & 0xF

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 05:22:21 pm
content: Quote from: brucehoult on May 05, 2022, 09:08:07 amYep.Quote from: brucehoult on May 05, 2022, 09:08:07 amAgain yep. One way of making sure some expression is going to be either 0 or 1 (unless as you said you use a relational operator, for which it's guaranteed), from C99 onwards, is to cast it to a bool.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 05:26:47 pm
content: Quote from: eugene on May 05, 2022, 05:21:46 pmProbably, on targets that do not have a barrel shifter. A shift by 6 can be just a single instruction and a single cycle on targets with a barrel shifter, but may be doable only with several instructions on some others. But note that the multiply by 9 also "hides" a shift (and then add) for targets for which the multiply is expensive.All that said, it's for the fun of optimizing, but even the most straightforward approach is usually more than good enough, and will be a lot more readable.

author: MK14
author_rank: Super Contributor
timestamp: May 05, 2022, 05:44:58 pm
content: Quote from: eugene on May 05, 2022, 05:21:46 pmThat doesn't seem to work, I tried it. Assuming I haven't messed up installing your code extract.Link to test version, with your code in it:https://godbolt.org/z/bc7Mq63zeIn theory, it shouldn't work correctly (if I'm analyzing it correctly), because both hex and 0x40, are non-zero (hence interpreted as true), so it (incorrectly) seems to add the 9 to the final answer (1 * 9 = 9), ruining the results.As regards efficiency (on the Arm M4 series cpu, I put in, since the actual architecture, hasn't been specified by the OP), it seems to need around 50% more (details at bottom of post) instructions, so could take around 50% longer to run. Also, it seems to use a conditional branch, which also can delay things further.If coded correctly (fixing the fact it doesn't seem to work correctly as specified), (as other(s) have sort of said, already), it could be an idea for improvement, on some architectures. Or at least is worth trying, if every last execution cycle matters.Results:Code:Hex 0 gives an int of 9Hex 1 gives an int of 10Hex 2 gives an int of 11Hex 3 gives an int of 12Hex 4 gives an int of 13Hex 5 gives an int of 14Hex 6 gives an int of 15Hex 7 gives an int of 0Hex 8 gives an int of 1Hex 9 gives an int of 2Hex A gives an int of 10Hex B gives an int of 11Hex C gives an int of 12Hex D gives an int of 13Hex E gives an int of 14Hex F gives an int of 15EDIT:Sorry, I forgot to put the instruction listings in. N.B. Fixing the fact the second code example, doesn't work correctly, could change the number of instructions, it produces.Code:HexToInt: MY ORIGINAL EXAMPLE, with bit shiftslsrs r2, r0, #6movs r3, #9mla r0, r2, r3, r0and r0, r0, #15bx lrHexToInt: THE NEW SUGGESTED modified way, using logical operator(s)cmp r0, #0ite nemovne r3, #9moveq r3, #0add r0, r0, r3and r0, r0, #15bx lr

author: eugene
author_rank: Frequent Contributor
timestamp: May 05, 2022, 05:55:53 pm
content: Quote from: MK14 on May 05, 2022, 05:44:58 pmIt does not work. What I was thinking was something likeCode:(hex & 0x40) >> 6in order to evaluate to either 0 or 1 depending on the state of bit 6.Code:hex && 0x40Will be true (==1) as long as hex is not zero.Sometimes I'm even dumber than usual!

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 06:40:25 pm
content: Quote from: eugene on May 05, 2022, 05:55:53 pmNote that, as I suggested above, casting it to a bool (in C99 onwards) will have the same effect and doesn't require two places to modify if you changed the bit to check.Code:(bool)(hex & 0x40)It does actually yield the exact same compiled code on most targets I've tried this with, but is easier to maintain and to read. (You can check using godbolt.org)

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 05, 2022, 10:12:55 pm
content: My thought is using bool will cause a conditional branch, it's not an arithmetic expression but a logic one, likely done by checking if it's zero or not.This does the same, doesn't branch, neither uses multiplication...((((c>>6)|(c>>6)<<3)+c&0xF);www.jdoodle.com/ia/qwcOtherwise, if branch is not a problem, just go the simple way

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 10:32:03 pm
content: Quote from: DavidAlfa on May 05, 2022, 10:12:55 pmNo, it doesn't. And I offered for anyone to check this using godbolt. It compiles EXACTLY the same.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 05, 2022, 10:43:57 pm
content: Sure, ARM will do thanks to the IT conditional. It's actually like a branch but without a branch. Nice instruction!PICs and other basic mcus will do branch, but not an issue since they have 0 flash wait states and no pipeline to fill.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 11:01:55 pm
content: Well, I have tried a lot of targets available and on all of them,Code:(hex & 0x40) >> 6(bool)(hex & 0x40)compile to exactly the same code. There's no need for any conditional here. Just a shift. ARM has instruction that can do this in just one instruction, otherwise it will take a couple, but in any case, no conditional, no branch, and the code is the same in both cases. Even on AVR, both lines compile to the same code. The only target I haven't checked is "PIC", whatever you mean by that (which PIC line? 8-bit I guess?), as godbolt doesn't have it as far as I've seen, and I don't have a Microchip compiler installed anymore except for PIC32.There may be some exceptions, for instance SDCC actually yields shorter code for the 'bool' cast version than the explicit shift version, go figure. But still no branch.Possibly some targets on which a branch is less expensive than a multi-bit shift (I guess that might be the case for 8-bit PIC? don't quite remember if it had a barrel shifter) would use a branch. But because it's less expensive. In which case, actually explicitely using a shift will yield less efficient code. So, even in that particular case, a cast to bool would win.

author: westfw
author_rank: Super Contributor
timestamp: May 05, 2022, 11:25:56 pm
content: QuoteSome ARMs. No "IT" on Cortex-M0 or M0+, for example.But what I came here to say is that you might want to worry about environments where "char" isn't ASCII.(it seems a little surprising that there isn't a well-defined function to do this. Most HLLs seem to only have the full "string" version, and no single-character version, and they're usually limited to the length of an integer. (although I guess some HLLs don't really have "char" types; just strings of length 1...)(insert (icky?) python code using a dictionary: " hextobin ={"0":0, "1":1, "2":2 ... "a":10, "A":10 ...})

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 05, 2022, 11:33:38 pm
content: Quote from: westfw on May 05, 2022, 11:25:56 pmI suppose we can safely assume that even if not ASCII-compliant, '0'-'9', 'A'-'F' and 'a'-'f' would be respectively consecutive, so the "straightforward" version I suggested (the one with input validation) would win both on readability, robustness and portability. =)

author: Ian.M
author_rank: Super Contributor
timestamp: May 06, 2022, 12:03:37 am
content: Yes, your 'long form' solution with validation should cope with EBCDIC, which none of the short-form solutions can handle due to assumptions about the ordering of digits and upper and lower case letters in the character set.EBCDIC code tableHowever on anything usingBaudot(teletype) code would have problems with the overlapped letter and number codes, only differentiated by the last Figure or Letter shift code encountered. Fortunately (IIRC) it was rarely used with computers except as a translation layer when interfacing with a baudot teletype as a terminal.

author: westfw
author_rank: Super Contributor
timestamp: May 06, 2022, 01:42:15 am
content: What happens in Unicode? For example, I notice that Cyrillic has their own ABC glyphs (and E too); not consecutive, though... Greek has some overlapping letters too. (do foreign programmers have to go through keyboard magic every time they type in a hex number?)

author: Ian.M
author_rank: Super Contributor
timestamp: May 06, 2022, 01:53:35 am
content: Unicode typically wont fit in a single char, requiring either multi-byte character strings to encode it e.g in UTF-8, or wide character support (wchar_t). I say typically, because on a CPU that cant efficiently do single byte access to registers or memory, the compiler implementers may choose to use a char type with more than eight bits.I don't know what non-western alphabet programmers do to conveniently enter 7 bit ASCII, but its worth noting that even high ASCII accented characters etc. can be problematic in C source.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 06, 2022, 01:56:20 am
content: Uh, yeah. I think the OP has more than enough material by now to move forward. =)And, I've never used a cyrillic keyboard (or chinese, or...), but typing latin characters must be a matter of using some function key or something. They do use latin characters for programming, and for a lot of other purposes.

author: brucehoult
author_rank: Super Contributor
timestamp: May 06, 2022, 04:01:26 am
content: Quote from: westfw on May 06, 2022, 01:42:15 amIf they're writing program code then they just leave their keyboard set to English. For the variable names and even comments too, usually.

author: brucehoult
author_rank: Super Contributor
timestamp: May 06, 2022, 04:07:48 am
content: Quote from: SiliconWizard on May 06, 2022, 01:56:20 amПочему нет?I have. They usually have both Cyrillic and English letters printed on the keycaps.Then you use some keystroke to switch languages. I use ctrl-windows-alt-spacebar on my Mac (with Microsoft kb). I do it constantly. Молодец!I actually type Russian using a non-standard layout where most Russian letters are typed using an English letter that sounds or looks similar.The layout:Code:щ 1 2 3 4 5 6 7 8 9 0 ь ъя ш е р т ы у и о п ю ж эа с д ф г ч й к л ; 'з х ц в б н м , . /

author: m k
author_rank: Super Contributor
timestamp: May 06, 2022, 08:18:09 am
content: Quote from: SiliconWizard on May 05, 2022, 05:22:21 pmFirst use of (X && 0x40) can be a clever try of something but second use reveals that it was just a simple brain fart.#define false 0#define true !false