Urgent - Split Long into 2 Int using BitShift

Hi All,
I have this problem and I did not found a solution :-/

I have a Signed Long variable that come from an encoder reading, I need to split it into 2 Signed Int to be able to send them to a PC using ModBus Slave over serial.

Actually I do this :

HighINT = ValueLong / 32767;
LowINT = ValueLong - (HighINT * 32767);

regs[MB_REG4] = HighINT;
regs[MB_REG5] = LowINT ;

It works good but it need a lot of time when ValueLong is big

I need to do the same operation using bitshift, something like:

HighINT = (ValueLong >> 16) & 0x0000ffffl;
LowINT = ValueLong & 0x0000ffffl;

regs[MB_REG4] = HighINT;
regs[MB_REG5] = LowINT;

I'm not expert in bits operation, I hope that someone can correct this code for me.

Thanks in advanced ! :wink:

Matrix

Dividing by 32767 'works good'?
That's where your problem is; your definition of 'good' is flawed.

yes it works but increasing the long number will increase the loop time from 1 mS to more than 4 mS and it is not good for me. Anyway the conversion works.

It doesn't work.
It may do something, but it isn't correct.
It may help to see the problem if you write 32767 out in hex.

EMH, I'm sure that it works.
I test it comparing the value of original Long on Arduino and the rebuilded one on the PC side doing the inverse math ( Software on PC side is VB6)

rebuildLong = ( HighINT * 32767) + LowInt

I need the same exactly results.... but faster :slight_smile:

You've already posted a bitshift method that works, so why not use it?

Looks to me like the shift you posted is correct and should work.

Question: The highint and lowint are they signed or unsigned?
One of the things that may happen with shift operations is sign extension, so maybe a cast to unsigned long is needed to make the >> 16 work correctly.


The first part is not correct:

HighINT = ValueLong / 32767;
LowINT = ValueLong - (HighINT * 32767);

should be

HighINT = ValueLong / 32768;
LowINT = ValueLong - (HighINT * 32768);

or

HighINT = ValueLong / 32768;
LowINT = ValueLong % 32768;

[edit]Sorry: the magic number must be 65536L as stated below. Too eager to answer :-[ [/edit]

Better still, use 65536. See reply #1

:wink:

Just use:

HighINT = ValueLong >> 16;
LowINT = ValueLong;

And be done with it. The masking is unnecessary. The results won't match your other method, but that is because, as AWOL has correctly pointed out, your other method is incorrect.

Both L & H int are signed and I need it because my Long must be Signed, Encoder can count + or - .

I dont know how BitShift works with sign :frowning:

The math of the first post works even if you divide the long value by any number lower that 32767, on the worst case I cant reach the maximum Long extension but it is a very remote possibility ( > 1 milion)
Anyway the problem is not the result but the speed of the loop.

I try with >> 16 as in the #1 post but did not work.

I try >>15 to leave 1 bit for sign and it works only for L value

I dont have the knowledge base to understand this bit operation.

HighINT = (ValueLong >> 16) & 0x0000ffffl;
LowINT  = ValueLong & 0x0000ffffl;

Those look OK. (better than the 32767 examples!)
There may be sign issues; Shift operation is not fully specified for signed numbers, but since you're masking things anyway you should get the correct results in any case.

Bitwise manipulation in general doesn't care one bit (no pun intended) about the sign or format of the variable they are operating on.

If you decompose a 4 byte variable into two 2 byte variables, and then later recompose them back into a 4 byte variable, the bit patterns will remain identical.

That's not to say there won't be any sign issues, but they will not be related in any way to the bitwise operations (for example, there may be endian problems if you are transferring the data between platforms with different endian'ness').

I try this code as test, maybe can help to find mistakes

int H;
int L;
long ValueLong;

void loop(){

ValueLong = -1;

H = ( ValueLong >> 16) & 0x0000ffffl;

L = ValueLong & 0x0000ffffl;

Serial.print(H,DEC);
Serial.print("-");
Serial.println(L,DEC);

delay(500);

}

Results are H = -1 L = -1

if ValueLong = 32769 H = 0 L = -32766 :frowning:

same results without masks

I'm counfused

try using print(..., BIN) to see the bit patterns ... thn you see what happens ..

With -1 as ValueLong I read on serial monitor:

L = 11111111111111111111111111111111
H = 11111111111111111111111111111111

both with mask or not

with 32769 I read:

H = 0
L= 11111111111111111000000000000001

with 32769 I read:

=> try 32769L (to be sure its a long)

With -1 as ValueLong I read on serial monitor:

L = 11111111111111111111111111111111
H = 11111111111111111111111111111111

because both L & H & ValueLong are signed, 1111...11 will read as -1 for all three of them. Bitwise it is a correct split.

Another way to split your long is to use an union, a less used construct in C++, A union has multiple var's mapped upon the same memory space:

union split_t 
{
  int I[2];
  long ValueLong;
} X;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  X.ValueLong = -1;
  
  int H = X.I[0];
  int L = X.I[1];
  
  Serial.print(H, DEC);
  Serial.print(" = ");
  Serial.println(L,DEC);
  delay(500);
}

The union split_t could be extended with unsigned long or an array of four bytes. Although it seems that no math is involved the indexing of the array takes some cpu cycles ...

Pay more attention to your data types.
BTW, with 32 bit data types, a one-in-a-million chance means you have over 4000 chances to screw up!

I'm not quite sure what your expectations are here. The intermediate int decimal values will in no way correlate to the original long decimal value. If they are only being used as a necessary step to transfer the entire data, then that doesn't matter.

What matters is that the bit representation is maintained.
Say you have a long value of (just for example) 1,582,081.
It's binary representation would be:
00000000 00011000 00100100 00000001

If you then assign that value to two ints with bitwise shifting, you would end up with
high int 00000000 00011000
low int 00100100 00000001

If you then properly recompose that back into a new long, that new long will have the same decimal value as the previous one (assuming the binary representation of that decimal value is the same across any platform transitions).

You could use unsigned ints for your intermediate variables and it wouldn't make a difference. You could even use 4 bytes or chars instead.

With -1 as ValueLong I read on serial monitor:

L = 11111111111111111111111111111111
H = 11111111111111111111111111111111

That looks completely correct. Signed ints are stored in a twos complement format. Google it for more info on how twos complement works, and why it's an ideal format to use for signed ints.

Pay more attention to your data types.
BTW, with 32 bit data types, a one-in-a-million chance means you have over 4000 chances to screw up!

Sorry but as I'm a noob in programming I have difficults to understand some things. Also english is not my language so... ::slight_smile:

Now I understand that variable are 32 bit long, this means that I can use Bitshift but in different way ?

because both L & H & ValueLong are signed, 1111...11 will read as -1 for all three of them. Bitwise it is a correct split.

Another way to split your long is to use an union, a less used construct in C++, A union has multiple var's mapped upon the same memory space:

I'll try with Union code, thanks for it, but I dont understand why bitshift did non work or I dont understand how to correctly read the variable sent

How are you putting your two 16 bit value back together as a 32 bit value?
Not still multiplying by 32767 I hope.