9

I'd like to write a function, in C, which takes the MSB of uint8_t, and if it's set, returns 0xFF and if not 0x00. In short, which returns an integer where all the bits are set to the same value as the MSB.

But I'd like to do it in a completely constant time way, no branches, no array offsets, just mathematical operations which are guaranteed to always touch the same number of bits. And ideally, without any undefined behavior. How can this be done?

Alex Gaynor
  • 14,353
  • 9
  • 63
  • 113
  • In C# I use the equivalent of `(uint8_t)((int8_t)x >> 7)`, but I'm not sure if that's well defined in C. – CodesInChaos Nov 19 '13 at 17:46
  • 2
    @CodesInChaos: It isn't; the result of right-shifting a signed value is implementation-defined, although in _practice_ you'd have a hard time finding an implementation that didn't compile it to an arithmetic shift. (Basically, the reason the C standard doesn't define it is because it doesn't assume that signed arithmetic is necessarily done using 2's complement.) – Ilmari Karonen Nov 19 '13 at 22:16

3 Answers3

6

How about:

#define uint8_msb_to_all_bits(x) (0xFF * ((x) >> 7))

or even better:

#define uint8_msb_to_all_bits(x) (-((x) >> 7))

The way these both work is that, if x is an 8-bit unsigned integer, then x >> 7 is 1 if the MSB of x is set, and 0 otherwise. All that remains is then mapping 1 to 0xFF, which can be done either by multiplication, or, in this particular case, simply by negating the number.

(Yes, negating an unsigned number is well defined in C.)

Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • Does negating an unsigned take constant time on modern CPUs? – Alex Gaynor Nov 18 '13 at 22:49
  • @AlexGaynor: I would be very surprised if it did not; it's likely to compile to a single constant-time assembly instruction. – Ilmari Karonen Nov 18 '13 at 22:50
  • Just because something is a single instruction doesn't mean it takes the same amount of time for all inputs (e.g. IDIV is variable time, as is anything that touches memory). – Alex Gaynor Nov 18 '13 at 22:53
  • 1
    Well, on x86 platforms it _probably_ compiles to a `neg` or a `sub` (unless a clever compiler optimizes it to a `sar`), neither of whose execution time _should_ depend on the inputs. But, of course, one can never really know in advance what some crazy compiler (or CPU designer!) might do, so the only way to be sure is to first inspect the assembly and then benchmark it. – Ilmari Karonen Nov 18 '13 at 23:06
  • 3
    Note that the second option will return `-1` with type `int` - if you want to have `0xFF`, it would be better to explicitly cast the result to `uint8_t`. – caf Nov 19 '13 at 00:35
5

What about

- (x >> 7)

?

Only the MSB is retained and math negation is used to replicate it to all bits.

6502
  • 112,025
  • 15
  • 165
  • 265
2

Given the declaration of your uint8_t:

uint8_t x = // MSB is either 1 or 0 if `x` depending on the signed value of `x`

A cheat that assumes 2's complement for signed integers:

return (((uint16_t)(int8_t)x) >> 8) & 0xff;
  • 2
    On systems that do use 2's complement arithmetic, you probably won't need the `(uint16_t)` cast, since simply right-shifting a signed value should compile to an [arithmetic shift](http://en.wikipedia.org/wiki/Arithmetic_shift). Of course, technically all this (starting with the `(int8_t)` cast) is implementation-defined behavior, so nothing can be guaranteed. – Ilmari Karonen Nov 18 '13 at 22:59
  • @IlmariKaronen Exactly... that cast is just for safety. Thanks for pointing this out, however. –  Nov 18 '13 at 23:01
  • @JensGustedt Why should that be 6, exactly? –  Nov 18 '13 at 23:13
  • 1
    @H2CO3 - one question - why have you added `& 0xff`? cast to `(int8_t)x` allows `x` to be later sign extended to 16 bits by `(uint16_t)`. Now `(uint16_t)(int8_t)x` is just 16bits value that has `x's` bit #7 copied to bits #8..#15. Right shift of unsigned value puts 0s to msbs so after `>>8` bits #8..#15 will be 0 regardless of `x's` msb. – Artur Dec 15 '13 at 12:22
  • @Artur just for safety. Better safe than sorry. –  Dec 15 '13 at 13:34