32 bit binary to 24 bit signed conversion - c++

I'm using a 24 bit I2C ADC with the Arduino and there is no 3 byte (24 bit) data type so I instead used the uint32_t which is a 32 bit unsigned int. My actual output however, is a 24 bit signed number as you can see below:
Also here is the code that I used to read the results if you're interested:
uint32_t readData(){
Wire.beginTransmission(address);
Wire.write(0x10);
Wire.endTransmission();
Wire.requestFrom(address,3);
byte dataMSB = Wire.read();
byte data = Wire.read();
byte dataLSB = Wire.read();
uint32_t data32 = dataMSB;
data32 <<= 8;
data32 |= data;
data32 <<= 8;
data32 |= dataLSB;
return data32;
}
In order for this number to be useful, I need to convert it back to a 24 bit signed integer (I'm not sure how to do that or eve if it's possible because 24 is not a power of 2) so I'm a bit stuck. It would be great if somebody can help me as I'm almost finished with the project and this is one of the last few steps.

The problem is that there’s no safe and portable way to use shifting for sign extension in C — at best it is implementation defined. So if you want to do it portably, you need to convert your 2s-complement value manually into a signed integer.
int32_t cvt24bit(uint32_t val) {
val &= 0xffffff; // limit to 24 bits -- may not be necessary
if (val >= (UINT32_C(1) << 23))
return (int32_t)val - (INT32_C(1) << 24);
else
return val;
}
this will take your 24-bit two’s-complement value in a uint32_t and convert it to a (signed) int32_t.

Conversion from 24-bit two’s complement in a uint32_t to int32_t can be done with:
int32_t Convert(uint32_t x)
{
int32_t t = x & 0xffffff;
return t - (t >> 23 << 24);
}
The x & 0xffffff ensures the number has no spurious bits above bit 23. If it is certain no such bits are set, then the statement can be just int32_t t = x;.
Then t >> 23 removes bits 0 to 22, leave just bit 23, which is the sign bit for a 24-bit integer. Then << 24 scales this, producing either 0 (for positive numbers) or 224 (for negative numbers). Subtracting that from t produces the desired value.

Use int32_t instead of unit32_t for data32. Then before returning the value, shift it left by 8, then right by 8 to sign extend it.
So this code:
uint32_t readData(){
Wire.beginTransmission(address);
Wire.write(0x10);
Wire.endTransmission();
Wire.requestFrom(address,3);
byte dataMSB = Wire.read();
byte data = Wire.read();
byte dataLSB = Wire.read();
int32_t data32 = dataMSB;
data32 <<= 8;
data32 |= data;
data32 <<= 8;
data32 |= dataLSB;
return (data32 << 8) >> 8;
}

Related

Compound operator with shift in C or how to convert 20bit 2'complement number in 32bit signed int

There is 20 bit 2'complement number (which is read with 3 x 8 bit) and it needs to be converted in 32bit signed int.
Could someone please explain this piece of code:
int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample /= 1L << 12;
So now in 32bit signed integer from right the right side there is 24 values and then:
sample /= 1L << 12;
How this works?
Link for the full code is at:
https://github.com/circuitar/Nanoshield_LoadCell/blob/master/src/Nanoshield_LoadCell.cpp
How this works?
If SPI.transfer(0) returns 0-255, it "works" when the 20-bit number is in the upper 24 bits of data read. Then shifting that into the 32-bit type sign-bit relies on UB to form the correct value that when divided but 1 << 12 is the sought after value.
To convert a 20-bit 2's complement number to a int32_t, test the sign bit.
// Alternative without UB nor implementation defined behavior:
int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
// if input is in upper 20 bits of the 24 read (OP is not explicit on that)
if (1) {
sample >>= 4;
}
assert(sample >= 0 && sample <= 0xFFFFF);
if (sample & 0x80000) { // Test for the 20-bit 2's complement sign bit
sample -= 0x100000;
}
In general, bit-wise shift operations should be done on unsigned integer types because:
Left-shifting a negative value results in undefined behavior.
Left-shifting a non-negative, signed value results in undefined behavior if any non-zero bits are shifted to or through the position of the sign bit.
Right-shifting a negative value produces an implementation-defined value.
If you are careful to avoid all of the above, bit-wise shift operations can be used portably on signed integer types as in the following1:
int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample >>= 4;
// 'sample' contains a 20-bit, 2's complement value. Sign-extend to a 32-bit signed value.
if (sample >= (INT32_C(1) << 19))
sample -= (INT32_C(1) << 20);
The if (sample >= ... sign-extension part might not be branch-free, depending on the compiler. An alternative sign-extension conversion from 20-bit 2's complement to 32-bit signed is:
sample = (sample ^ (INT32_C(1) << 19)) - (INT32_C(1) << 20);
The XOR operation converts the 2's complement value to an offset binary value. That operation could be merged into the value of the byte from the first SPI transfer (the most significant 8 bits of the 20-bit sample value) as follows:
int32_t sample = 0;
sample |= SPI.transfer(0) ^ 0x80; // convert 2's complement to offset binary
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample >>= 4;
// 'sample' contains a 20-bit, offset binary value. Convert to 32-bit, signed value.
sample -= INT32_C(1) << 20;
1 "Portably" here as long as int32_t is provided by the implementation. If not, then int_least32_t can be used instead.

How to calculate the Hexa value from the IP checksum field?

I have a unsigned short buffer , it contain a hexa values of IP header .
unsigned short buffer = {45,00,00,4E,4E,05,00,10,80,11,0X00,0X00,0A,00,00,1C,1A,00,00,2F};
I added 0X00 , 0X00 in the place of Checksum filed and calculated checksum with the RFC 1071 - Calculating IP header checksum . So it is returning a unsigned short checksum value . Now i need to insert to the Ip buffer .
For example : if my unsigned short checksum value = 55168 and it's Hex value = D780 . Now i need to add D7 , 80 to buffer[11] and buffer[12] . How can i convert unsigned short checksum value and split the Hex value for inserting to the buffer ? If it Hex = D78 , then also i need to add 0D and 78 to the buffer field .
You can separate the values using the bit operators
buffer[11] = (checksum >> 8) & 0xff;
buffer[12] = checksum & 0xff;
The first line shifts the checksum by 8 bits to the right, which is equivalent to dividing by 256 and than ensures, that you only get the information of those remaining 8 bits (0xD7) by using the and(&) operator with 0xff which equals to 1111 1111 in binary representation.
The second line also uses the and operator to save ONLY the last 8 bit of your checksum, which would be 0x80.
You can use expressions of the form buffer[11] = (checkSum & 0xFF00) >> 8; buffer[12] = (checkSum & 0xFF), but you should carefully check if your processor architecture uses big endian or little endian form for representing integers. Hence, it may be that on some systems you have to exchange the expressions to buffer[12] = (checkSum & 0xFF00) >> 8; buffer[11] = (checkSum & 0xFF).
See the following code:
#include <stdio.h>
unsigned short getCheckSum(unsigned short buffer[]) {
return 0xD780;
}
int main(int argc, const char *argv[])
{
unsigned short buffer[20] = {0x45,0x00,0x00,0x4E,0x4E,0x05,0x00,0x10,0x80,0x11,0X00,0X00,0x0A,0x00,0x00,0x1C,0x1A,0x00,0x00,0x2F};
unsigned short checkSum = getCheckSum(buffer);
buffer[11] = (checkSum & 0xFF00) >> 8;
buffer[12] = (checkSum & 0xFF);
for (int i=0; i<20; i++) {
printf("%02X ", buffer[i]);
}
return 0;
}

C++ How to combine two signed 8 Bit numbers to a 16 Bit short? Unexplainable results

I need to combine two signed 8 Bit _int8 values to a signed short (16 Bit) value. It is important that the sign is not lost.
My code is:
unsigned short lsb = -13;
unsigned short msb = 1;
short combined = (msb << 8 )| lsb;
The result I get is -13. However, I expect it to be 499.
For the following examples, I get the correct results with the same code:
msb = -1; lsb = -6; combined = -6;
msb = 1; lsb = 89; combined = 345;
msb = -1; lsb = 13; combined = -243;
However, msb = 1; lsb = -84; combined = -84; where I would expect 428.
It seems that if the lsb is negative and the msb is positive, something goes wrong!
What is wrong with my code? How does the computer get to these unexpected results (Win7, 64 Bit and VS2008 C++)?
Your lsb in this case contains 0xfff3. When you OR it with 1 << 8 nothing changes because there is already a 1 in that bit position.
Try short combined = (msb << 8 ) | (lsb & 0xff);
Or using a union:
#include <iostream>
union Combine
{
short target;
char dest[ sizeof( short ) ];
};
int main()
{
Combine cc;
cc.dest[0] = -13, cc.dest[1] = 1;
std::cout << cc.target << std::endl;
}
It is possible that lsb is being automatically sign-extended to 16 bits. I notice you only have a problem when it is negative and msb is positive, and that is what you would expect to happen given the way you're using the or operator. Although, you're clearly doing something very strange here. What are you actually trying to do here?
Raisonanse C complier for STM8 (and, possibly, many other compilers) generates ugly code for classic C code when writing 16-bit variables into 8-bit hardware registers.
Note - STM8 is big-endian, for little-endian CPUs code must be slightly modified. Read/Write byte order is important too.
So, standard C code piece:
unsigned int ch1Sum;
...
TIM5_CCR1H = ch1Sum >> 8;
TIM5_CCR1L = ch1Sum;
Is being compiled to:
;TIM5_CCR1H = ch1Sum >> 8;
LDW X,ch1Sum
CLR A
RRWA X,A
LD A,XL
LD TIM5_CCR1,A
;TIM5_CCR1L = ch1Sum;
MOV TIM5_CCR1+1,ch1Sum+1
Too long, too slow.
My version:
unsigned int ch1Sum;
...
TIM5_CCR1H = ((u8*)&ch1Sum)[0];
TIM5_CCR1L = ch1Sum;
That is compiled into adequate two MOVes
;TIM5_CCR1H = ((u8*)&ch1Sum)[0];
MOV TIM5_CCR1,ch1Sum
;TIM5_CCR1L = ch1Sum;
MOV TIM5_CCR1+1,ch1Sum+1
Opposite direction:
unsigned int uSonicRange;
...
((unsigned char *)&uSonicRange)[0] = TIM1_CCR2H;
((unsigned char *)&uSonicRange)[1] = TIM1_CCR2L;
instead of
unsigned int uSonicRange;
...
uSonicRange = TIM1_CCR2H << 8;
uSonicRange |= TIM1_CCR2L;
Some things you should know about the datatypes (un)signed short and char:
char is an 8-bit value, thats what you where looking for for lsb and msb. short is 16 bits in length.
You should also not store signed values in unsigned ones execpt you know what you are doing.
You can take a look at the two's complement. It describes the representation of negative values (for integers, not for floating-point values) in C/C++ and many other programming languages.
There are multiple versions of making your own two's complement:
int a;
// setting a
a = -a; // Clean version. Easier to understand and read. Use this one.
a = (~a)+1; // The arithmetical version. Does the same, but takes more steps.
// Don't use the last one unless you need it!
// It can be 'optimized away' by the compiler.
stdint.h (with inttypes.h) is more for the purpose of having exact lengths for your variable. If you really need a variable to have a specific byte-length you should use that (here you need it).
You should everythime use datatypes which fit your needs the best. Your code should therefore look like this:
signed char lsb; // signed 8-bit value
signed char msb; // signed 8-bit value
signed short combined = msb << 8 | (lsb & 0xFF); // signed 16-bit value
or like this:
#include <stdint.h>
int8_t lsb; // signed 8-bit value
int8_t msb; // signed 8-bit value
int_16_t combined = msb << 8 | (lsb & 0xFF); // signed 16-bit value
For the last one the compiler will use signed 8/16-bit values everytime regardless what length int has on your platform. Wikipedia got some nice explanation of the int8_t and int16_t datatypes (and all the other datatypes).
btw: cppreference.com is useful for looking up the ANSI C standards and other things that are worth to know about C/C++.
You wrote, that you need to combine two 8-bit values. Why you're using unsigned short then?
As Dan already said, lsb automatically extended to 16 bits. Try the following code:
uint8_t lsb = -13;
uint8_t msb = 1;
int16_t combined = (msb << 8) | lsb;
This gives you the expected result: 499.
If this is what you want:
msb: 1, lsb: -13, combined: 499
msb: -6, lsb: -1, combined: -1281
msb: 1, lsb: 89, combined: 345
msb: -1, lsb: 13, combined: -243
msb: 1, lsb: -84, combined: 428
Use this:
short combine(unsigned char msb, unsigned char lsb) {
return (msb<<8u)|lsb;
}
I don't understand why you would want msb -6 and lsb -1 to generate -6 though.

Given an array of uint8_t what is a good way to extract any subsequence of bits as a uint32_t?

I have run into an interesting problem lately:
Lets say I have an array of bytes (uint8_t to be exact) of length at least one. Now i need a function that will get a subsequence of bits from this array, starting with bit X (zero based index, inclusive) and having length L and will return this as an uint32_t. If L is smaller than 32 the remaining high bits should be zero.
Although this is not very hard to solve, my current thoughts on how to do this seem a bit cumbersome to me. I'm thinking of a table of all the possible masks for a given byte (start with bit 0-7, take 1-8 bits) and then construct the number one byte at a time using this table.
Can somebody come up with a nicer solution? Note that i cannot use Boost or STL for this - and no, it is not a homework, its a problem i run into at work and we do not use Boost or STL in the code where this thing goes. You can assume that: 0 < L <= 32 and that the byte array is large enough to hold the subsequence.
One example of correct input/output:
array: 00110011 1010 1010 11110011 01 101100
subsequence: X = 12 (zero based index), L = 14
resulting uint32_t = 00000000 00000000 00 101011 11001101
Only the first and last bytes in the subsequence will involve some bit slicing to get the required bits out, while the intermediate bytes can be shifted in whole into the result. Here's some sample code, absolutely untested -- it does what I described, but some of the bit indices could be off by one:
uint8_t bytes[];
int X, L;
uint32_t result;
int startByte = X / 8, /* starting byte number */
startBit = 7 - X % 8, /* bit index within starting byte, from LSB */
endByte = (X + L) / 8, /* ending byte number */
endBit = 7 - (X + L) % 8; /* bit index within ending byte, from LSB */
/* Special case where start and end are within same byte:
just get bits from startBit to endBit */
if (startByte == endByte) {
uint8_t byte = bytes[startByte];
result = (byte >> endBit) & ((1 << (startBit - endBit)) - 1);
}
/* All other cases: get ending bits of starting byte,
all other bytes in between,
starting bits of ending byte */
else {
uint8_t byte = bytes[startByte];
result = byte & ((1 << startBit) - 1);
for (int i = startByte + 1; i < endByte; i++)
result = (result << 8) | bytes[i];
byte = bytes[endByte];
result = (result << (8 - endBit)) | (byte >> endBit);
}
Take a look at std::bitset and boost::dynamic_bitset.
I would be thinking something like loading a uint64_t with a cast and then shifting left and right to lose the uninteresting bits.
uint32_t extract_bits(uint8_t* bytes, int start, int count)
{
int shiftleft = 32+start;
int shiftright = 64-count;
uint64_t *ptr = (uint64_t*)(bytes);
uint64_t hold = *ptr;
hold <<= shiftleft;
hold >>= shiftright;
return (uint32_t)hold;
}
For the sake of completness, i'am adding my solution inspired by the comments and answers here. Thanks to all who bothered to think about the problem.
static const uint8_t firstByteMasks[8] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
uint32_t getBits( const uint8_t *buf, const uint32_t bitoff, const uint32_t len, const uint32_t bitcount )
{
uint64_t result = 0;
int32_t startByte = bitoff / 8; // starting byte number
int32_t endByte = ((bitoff + bitcount) - 1) / 8; // ending byte number
int32_t rightShift = 16 - ((bitoff + bitcount) % 8 );
if ( endByte >= len ) return -1;
if ( rightShift == 16 ) rightShift = 8;
result = buf[startByte] & firstByteMasks[bitoff % 8];
result = result << 8;
for ( int32_t i = startByte + 1; i <= endByte; i++ )
{
result |= buf[i];
result = result << 8;
}
result = result >> rightShift;
return (uint32_t)result;
}
Few notes: i tested the code and it seems to work just fine, however, there may be bugs. If i find any, i will update the code here. Also, there are probably better solutions!

C++ Reading signed 32-bit integer value

I have a multi-byte primitive type called s32 which I want to read from a byte array.
The specifications are:
It is a 32-bit signed integer value, stored in little-endian order.
Negative integers are represented using 2's complement.
It uses 1 to 5 bytes depending on the magnitude. Each byte contributes its low seven bits to the value. If the high (8th) bit is set, then the next byte is also a part of the value.
Sign extension is applied: the seventh bit of the last byte of the encoding is propagated to fill out the 32 bits of the decoded value.
In the case of U32 - unsigned 32-bit I come up with this (any comments welcomed!) but not sure how to modify it for S32.
char temp = 0;
u32 value = 0;
size_t index = 0;
for(int i = 0; i < 5; i++)
{
if(i < 4)
{
temp = 0x7F & buffer[index];
}
else
{
temp = 0x0F & buffer[index];
}
value |= temp << (7 * i);
if(!(0x80 & buffer[index])) break;
++index;
}
Thanks everyone!
Are you working on a little-endian system?
If so following should do the trick.
if(!(0x80 & buffer[index]))
{
if(0x40 & buffer[index]) value = -value;
break;
}
If you need the negative of a little endian value on a big endian system, then it is a bit more tricky, but that requirement would seem very strange to me
I posted a SIGN_EXTEND macro in an answer to this question. For your code, I'd change your u32 value to s32 value, and apply SIGN_EXTEND as
// after loop close
SIGN_EXTEND(value, index * 7, u32);
using the accepted answer for the question, you'd say:
if(value > (1 << (index * 7 - 1))
value -= (1 << (index * 7));