Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting High & Low 16 bit Words from a 32 bit number

Tags:

c

mask

I have the following code that " functions " in that I can write a 32 bit counter value and place it in the High and Low words of the 32 bit register in a dsPic. I am using MPLAB X IDE and the XC16 compiler.

int main()
{
  unsigned long My_Number;      // 32 bit
  unsigned int High_Word;       // 16 bit 
  unsigned int Low_Word;        // 16 bit    
    
  My_Number = 0x6000C000;       // Decimal 1610661888

  High_Word = (My_Number & (0x1FFFF << 16)) >> (16);     // Returns 6000
 
  Low_Word = (My_Number & 0xFFFF ) ;                     // Returns C000  

  return 0;
}

What I am not understanding is why I need to use 1FFFF to mask the High_Word.

I have tried different values to find what gets returned to find an explanation but something is not clicking and I don't like having anything that works without understanding why as that always leads to issues down the line.

Edit: Having read all the replies and done some re-thinking I have realised that rather than using 1FFFF I could have used FFFFUL to force it to an unsigned long integer and that works. Going further I can see that rather than shifting left to then shift right I could just use

High_Word = (My_Number >> 16);
Low_Word = My_Number;

This code returns the expected values and I believe is more compliant.

like image 441
spectric Avatar asked Dec 11 '25 00:12

spectric


1 Answers

The comments seem to indicate that int and unsigned int are 16 bits in the targeted C implementation, and that long int and unsigned long int are 32 bits.

In such a C implementation 0xFFFF is an unsigned int per the table in C 2018 6.4.4.1 5. This means that 0xFFFF << 16 will take FFFF16 as a 16-bit item and ask to shift it 16 bits. Then the behavior of the program is not defined because C 2018 6.5.7 3, about shifts, says:

… If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

However, if we write 0x1FFFF, this is too large for an unsigned int. Then the table tells us it is a long int, which is 32 bits.

Now 0x1FFFF << 16 attempts to shift a 32-bit item by 16 bits.

It may be the author of the code thought this code would have defined behavior, since it no longer falls afoul of the rule in 6.5.7 3.

Unfortunately, it does fall afoul of a different rule in 6.5.7 4:

… If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

In other words, while 0x1FFFF is 32 bits, it is a signed type, and the signed type can only represent values up 7FFFFFFF16. The mathematical product for the shift would be 1FFFF000016, which does not fit. So the behavior is not defined.

Had the author written 0x1FFFFu to request an unsigned type or 0xFFFFul to request an unsigned long int, then the behavior would be defined. That is because, for shifting an unsigned operand, the result is defined even if the product is not representable: Excess bits are discarded (as specified in the C standard, the result is wrapped modulo a modulus for the type).

I conjecture that, in the targeted C implementation 0x1FFFF << 16 produces FFFF000016 in the bits of a long int, and that is why the author used that code. Even though the C standard does not define the behavior, it may work. In contrast, 0xFFFF << 16 likely produces only a 16-bit result, which cannot be correct since FFFF16 is needed in the high bits.

like image 112
Eric Postpischil Avatar answered Dec 13 '25 16:12

Eric Postpischil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!