[SOLVED] Splitting and Rebuilding of long data type numbers

To give some sort of context, I am essentially looking to store large(ish) numbers on an RFID tag (Mifare classic 1kb) and understand I need to split the numbers I have, into bytes so they can then be written to the RFID tag. Equally, I will want to read this number from the RFID tag and therefore I need to rebuild the number I had split (I am aware that the Mifare cards store their bytes as Hex).

To achieve this I have been scouring the forums and other parts of the internet to get the answer, however after thinking I'd cracked it, I've come up short.

I have, what I believe is a 32 bit number as a 'long' data type, with a maximum number of 2,147,483,647 (I'm only interested in it being in the positive).
I split this into 4 separate bytes, by using Bit shift/wise operators. I print these to see the values in Binary.

I then try to 'rebuild' the original number by using bitwise 'OR' operators.

Below is a short bit of code that I have been using to try and test the splitting and rebuilding of numbers:

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
long num = 2147483643;
Serial.println(num, DEC);
Serial.println(num, BIN);
byte b1 = (num >> 24);
byte b2 = (num >> 16);
byte b3 = (num >> 8);
byte b4 = (num);

Serial.println(b1,BIN);
Serial.println(b2,BIN);
Serial.println(b3,BIN);
Serial.println(b4,BIN);

Serial.println("rebuilding below");
//Start rebuilding number from bytes above
long numRebuild = (b1<<24)|(b2<<16)|(b3<<8)|b4;
Serial.println(numRebuild, DEC);
Serial.println(numRebuild, BIN);

//Using unsigned long gives double the original 'num'
unsigned long numRebuild2 = (b1<<24)|(b2<<16)|(b3<<8)|b4;
Serial.println(numRebuild2, DEC);
Serial.println(numRebuild2, BIN);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Here are the results I get (to make it easier to read I have added result Lines):

Line 1 - 22:45:16.504 -> 2147483643
Line 2 - 22:45:16.504 -> 1111111111111111111111111111011
Line 3 - 22:45:16.571 -> 1111111
Line 4 - 22:45:16.571 -> 11111111
Line 5 - 22:45:16.571 -> 11111111
Line 6 - 22:45:16.571 -> 11111011
Line 7 - 22:45:16.606 -> rebuilding below
Line 8 - 22:45:16.606 -> -5
Line 9 - 22:45:16.606 -> 11111111111111111111111111111011
Line 10 - 22:45:16.641 -> 4294967291
Line 11 - 22:45:16.676 -> 11111111111111111111111111111011

The results aren't as expected. When rebuilding the new Long numRebuild I get a -5, which I'm not understanding, and equally, for numRebuild2, I am getting the over the maximum available number of an 'Unsigned Long' datatype: it is the maximum available (to my understanding) + 1!

Now, I can see that the Binary values on numRebuild and numRebuild2 match each other, but both of these have an extra bit (32bits instead of 31) from the original long of num.
I believe this is helping identify whether it's positive or negative, however I have tried to account for this by shifting the first byte, as well as trying to force a '0' at the very beginning, to no such luck.

Is anyone able to shed any light on why I am getting these numbers as I don't quite fully understand, as well as a potential fix, or a hint at what other terms I should perhaps be searching for?
Thanks in advance, and apologies for the wordy post!

If you turn on all warnings in your IDE preferences, you will see a warning about the left shift operator exceeding the type. Basically, it is taking your byte variables and shifting them 24 bits, but they are only 8 bits wide. You need to cast them as long.

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
long num = 2147483643;
Serial.println(num, DEC);
Serial.println(num, BIN);
byte b1 = (num >> 24);
byte b2 = (num >> 16);
byte b3 = (num >> 8);
byte b4 = (num);

Serial.println(b1,BIN);
Serial.println(b2,BIN);
Serial.println(b3,BIN);
Serial.println(b4,BIN);

Serial.println("rebuilding below");
//Start rebuilding number from bytes above
long numRebuild = ((long)b1<<24)|((long)b2<<16)|((long)b3<<8)|(long)b4;
Serial.println(numRebuild, DEC);
Serial.println(numRebuild, BIN);

//Using unsigned long gives double the original 'num'
unsigned long numRebuild2 = ((unsigned long)b1<<24)|((unsigned long)b2<<16)|((unsigned long)b3<<8)|b4;
Serial.println(numRebuild2, DEC);
Serial.println(numRebuild2, BIN);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Consider using a union to do the same thing:

//..\Union Example\union_example.ino
//
typedef union u_conv
{
    long    lValue;
    byte    lByte[4];
        
};

u_conv
    strt,
    finish;
        
void setup() 
{
    Serial.begin(9600);
    
    strt.lValue = 2147483643;

    Serial.println("Original values:");
    Serial.println(strt.lValue, DEC);
    Serial.println(strt.lValue, BIN);

    //send start.lByte[0..3] to storage medium
    //
    //here, the retrieval is simulated by copying the byte values from the "strt" union
    //to the corresponding byet values in the "finish" union
    //
    //in use, you'd retrieve the bytes from storage and put them in a destination union
    for( int i=0; i<4; i++ )
        finish.lByte[i] = strt.lByte[i];

    Serial.println("Processed values:");
    Serial.println(finish.lValue, DEC);
    Serial.println(finish.lValue, BIN);

}//setup

void loop() 
{

}//loop

Thank you both for your responses.
@blh64 I have tried this and it has worked :slight_smile: many thanks!
Simple and easy to implement. I will try to find more documentation, but are you able to provide a quick explanation as to why this works?

@Blackfin, thanks for your response as well. I did look at unions to solve this (many solutions indicated to use unions) however I wasn't really understanding them. As you've kindly supplied a 'direct' example that would work for me, I'll study this and see if I can make a bit more sense of it :).

On the topic of Unions, on my travels of solutions (on many forums!) I thought I had read that unions take up a bit more memory/processing than other solutions (for example the one I've tried). Are you able to shed any light on this?

Again thanks to you both.

@OP

The post of this link may help you to understand the working principle of union data structure.

the default type of an Uno is a 16 bit integer so when you take an unsigned 8 bit unsigned integer (byte) and shift it 24 bits, the compiler uses integer math and the result no longer fits within a 16 bit integer.

On the other hand, if you take an 8 bit unsigned integer and cast it as a long, it is now 32 bits wide and can easily be shifted 24 bits to the left.

The compiler is stingy and tries to use the smallest possible type which occasionally bites you.

Thanks everyone for your help. I've resolved what I set out to do, and learnt a bit more which is always welcomed!