... load up the 10 bits into a 16-bit int and extend (copy) the sign in bit 9 from bit 10 to bit 15 ...
It's even easier than that. The device provides a 10-bit signed number, left-justified in a 16-bit word. For a 16-bit platform, it's as simple as loading the value into a signed int, and shifting it six bits to the right; the sign bit is automatically extended. But that's not what the OP asked for: he asked for help in writing code that could easily accommodate a different number of significant bits from the input device, and he expressed concern about algorithms that relied on a 16-bit integer size. Because the issue has practical implications, it's getting a bit of attention. It's certainly made me wonder how deeply I've embedded implementation-dependent parameters into some of my own favorite code.
For a 16-bit platform, it's as simple as loading the value into a signed int ** with the top bit as bit 15, maybe load it direct, shift 6 bits left after determining 6 bits for 16 bit int or some other way but there has to be that trick step or the next trick won't work **, and shifting it six bits to the right; the sign bit is automatically extended.
When you put in all the steps you get the true length of the code. Then the other simple ways don't seem so bad.
The best routine would run completely on CPU registers in less a microsecond, but that wouldn't be 'generic'.
Why? Did 16-bit platforms change? I wrote a lot of code on 8080's and 8088's and never had trouble with bits then. Come to think, I did a lot of 16 bit word code on 6502's (8 bit CPU, 16 bit languages) as well. No trouble with bits, they have bit rotate as well as bit shift commands.
When I use 16-bit ints on Arduino that uses 16-bit addressing, what is the platform? Isn't that set by address length?
Note that I quoted someone else in that post and added a step I saw left out. But yah that RTC doesn't give a straight 10 bits, does it? The bits have to practically be mapped to the target. Good news is 32-bits only needs either 0x0000 or 0xFFFF added to a 16-bit solution.
Having reflected a bit more on this, I see that in this code:
const long deviceBits = 10;
const long deviceWordLength = 16;
const long deviceMask = (1 << deviceBits) - 1;
there's no need for any of the constants to be declared long. The counting constants, describing length of the significant data and length of the word it resides in, can obviously be byte-sized, at least until we start using 257-bit integers. The mask doesn't need to be any bigger than the input data word, in this case int.
That's not the mask I wanted. I wanted this one:const long deviceMask = -(1 << (deviceWordLength - 1));The mask isolates the sign bit for testing, and extends the sign bit when the sign bit is set. It needs to be as long as the type of the input, which is int in this case, but it doesn't need to be longer.
I think you will find that a generic approach is not as optimal as having a series of specific approaches (one for 10-bits, one for 12-bits, etc.) that you can link in for your specific applications.
If those numbers came from an adc module, many allow left-aligned adcs making the shift approach a lot more appealing.