5 years ago.

How to combine two Char's to get an integer value

Got a bit stuck with probably basic C principles here :(

I need to combine two sequential characters to generate an integer without using anything heavyweight like printf / scanf.

The snip below shows case 7 and case 8 that need to be combined to get the integer value as fast as possible.

The received values will depend on the connection data, this example is 48, but could be anything up to 2048.

snip

            case 0:if(rxByte=='+')state=1;break;
            case 1:if(rxByte=='I')state=2;else state=0;break;
            case 2:if(rxByte=='P')state=3;else state=0;break;
            case 3:if(rxByte=='D')state=4;else state=0;break;
            case 4:if(rxByte==',')state=5;else state=0;break;
            case 5:if(rxByte==id) state=6;else state=0;break;
            case 6:if(rxByte==',')state=7;else state=0;break;
            case 7://(rxByte here is the 1st character is ascii 52 symbol 4)      state=8;else state=8;break;
            case 8://(rxByte here is the 2nd character is ascii 56 symbol 8)      state=9;else state=9;break;            
            case 9:if(rxByte==':')state=10;else state=0;break;                
            case 10:
           
            
            if(reqLinBuffIndex!= // need the result of case 7 and case 8 here, should be integer 48 in this example    ){

Edit ....

This seems to be working putting the two characters in an array and using atoi.

Not sure if this is the best way.

            case 7:rxAmount[0]=rxByte;state=8;break;       
            case 8:rxAmount[1]=rxByte;state=9;break;                    
            case 9:if(rxByte==':')state=10;else state=0;break;                
            case 10:data[reqLinBuffIndex]=rxByte;
                                                
                    if(!x){recv_amount=atoi(rxAmount);x=1;}

2 Answers

5 years ago.

It it's always two characters then the simple solution is to use the fact that the acsii values are in number order and do:

int result = 0;

switch (state){
...
case 7: result =(rxByte-'0')*10;  state=8; break;
case 8: result +=(rxByte-'0');  state=9; break;
...
}

If you want to cope with any length of numbers then you end up with something similar to the last example in David's answer;

int result = 0;
case 7: 
  if ((rxByte>='0') && (rxByte<='9')) {
    result *=10;
    result +=  rxByte-'0';
  } else {
    state=9; // the state to jump to after the number ends. 
     // If you need to do something with the first non-numeric character then check it in this else
  }
   break;

If this a function that is called once per byte then hopefully it's obvious that you can't set result to 0 at the start of the function as I did, instead do it in the state before you receive the numbers. And in that situation the result variable would ideally be static within the function or alternatively global.

Accepted Answer

David, Andy,

Many thanks for your help, I went with Andy's solution as it was the simplest.

I'm re-writing an ESP8266 Wi-Fi interface driver for OS2 where speed is critical to handle dynamic web pages. I had to ditch most of the parser functions and buffered serial as they were too slow. Now works just fine :)

The idea (not mine) is to only get the first few important characters we need to handle the web request, the rest is ignored, this makes a big difference.

uint32_t ESP8266::recv(uint32_t id ,char *data ,uint32_t amount)
{    
    char rxByte;
    static uint32_t rxAmount=0;
    static char state=0;
    while(bufferReadIndex_IpdSearch!=bufferWriteIndex){
        rxByte=rxBuff[bufferReadIndex_IpdSearch++];
        if(bufferReadIndex_IpdSearch>=BUFFER_SIZE){bufferReadIndex_IpdSearch=0;}
        
        switch(state){
            case 0:if(rxByte=='+')state=1;break;
            case 1:if(rxByte=='I')state=2;else state=0;break;
            case 2:if(rxByte=='P')state=3;else state=0;break;
            case 3:if(rxByte=='D')state=4;else state=0;break;
            case 4:if(rxByte==',')state=5;else state=0;break;
            case 5:if((rxByte-'0')== id)state=6;else state=0;break;
            case 6:if(rxByte==',')state=7;else state=0;break;                        
            case 7: rxAmount =(rxByte-'0')*10; state=8; break;
            case 8: rxAmount +=(rxByte-'0'); state=9; break;                              
            case 9:if(rxByte==':')state=10;else state=0;break;                
            case 10:   
                    data[reqLinBuffIndex]=rxByte;
                    reqLinBuffIndex++;
                    
                    if(reqLinBuffIndex>=rxAmount && amount==rxAmount){                     
                        data[reqLinBuffIndex]=0;
                        state=0;reqLinBuffIndex=0;
                        return rxAmount;
                    }
        }
    }
    return 0;
}

posted by Paul Staron 06 Mar 2019
5 years ago.

hi Paul,

I'm not entire sure I understand your data source. so I'll generalize to see if I get at all close to helping -

	uint8_t array[5] = { 0x31, 0x32, 0x33, 0x34, 0x35 };

	uint16_t test1 = *(uint16_t*) & array[0];
	uint32_t test2 = *(uint32_t*) & array[1];

	printf("test1 = 0x%04X, test2 = 0x%08X\n", test1, test2);
	// prints: test1 = 0x3231, test2 = 0x35343332

ARM micros are little endian, meaning that a value in memory that is greater than 1 byte has successively higher order value in the increasing memory locations.

test values are pulled from the 'byte' array, which could represent keyboard input, or some other data stream. If you look at that byte stream as the typed sequence "12345", the user might be entering a large number. atoi() would be among the easiest conversions to extract a big number.

Note how the taking the address of one of the bytes, and then extracting the multi-byte value as either 16 or 32 bits in size, pulls the data in the reverse order - which is the little-endian storage order for values > 8 bits.

using the same data as above,

uint32 test3 = atoi(array);
printf("test3 = %d", test3);
// prints test3 = 12345

you might need atoi((char *)array) to type-match the atoi() function.

Another way to pull the value from an input stream without external functions -

char * p = (char *)array;
unsigned int test4 = 0;
while (*p >= '0' && *p <= '9') {
    test4 = 10 * test4 + *p - '0';
    p++;
}
printf("test4 = %d", test4); // prints 12345