26

The website in which I found this code

int v, sign;
// or, to avoid branching on CPUs with flag registers (IA32):
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));  // if v < 0 then -1, else 0. 

This statement assigns variable sign with the sign of variable v (either -1 or 0). I wonder why (int)((unsigned int)((int)v) is used instead of a plain v?

nalzok
  • 14,965
  • 21
  • 72
  • 139
  • @chux http://stackoverflow.com/a/4009954/1294207 Maybe? – Fantastic Mr Fox Nov 23 '15 at 23:29
  • 5
    @chux: it is not UB, merely implementation defined. C11 6.5.7: *...If E1 has a signed type and a negative value, the resulting value is implementation-defined*. The comment does not seem to make sense. – chqrlie Nov 23 '15 at 23:50
  • Suggest adding `int v; int sign;` from the site. – chux - Reinstate Monica Nov 23 '15 at 23:59
  • @chux: The comment relates to a different issue, namely extracting the sign bit without a test and branch. Whether the CPU has flag registers is irrelevant. The issue about right shifting signed types with negative values is not commented, but should be. – chqrlie Nov 24 '15 at 00:20
  • Why is it faster than `sign = -(v < 0)`? – user3528438 Nov 24 '15 at 16:39

3 Answers3

32

Note that you've extracted a fragment of the expression in your question (you quote (int)((unsigned int)((int)v) which has one more left bracket ( than it does right brackets )). The RHS expression of the assignment statement is, in full:

-(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

If you add a few spaces, you find:

-(int) (  (unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)  );
       ^  ^            ^^      ^    ^                          ^  ^
       |  +------------++------+    +--------------------------+  |
       +----------------------------------------------------------+

That is, the outer (int) cast applies to all of:

((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));

The inner cast to (int) cast is vacuous; its result is immediately cast to unsigned int. The (unsigned int) cast ensures that the right shift is well defined. The expression as a whole determines whether the most significant bit is a 0 or a 1. The outer int converts the result back to an int, and the - then negates it, so the expression is -1 if v is negative and 0 if v is zero or positive — which is what the comment says.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • The comment just says that you can implement the function as an arithmetic expression without testing and branching. The subtle need for casts is not documented. – chqrlie Nov 23 '15 at 23:53
  • What do you mean by "vacuous"? It's certainly not redundant (for some `v`, `(unsigned int)(int)v` is different to `(unsigned int)v`) – M.M Nov 23 '15 at 23:57
  • @M.M: would you care to give an illustration? – Jonathan Leffler Nov 24 '15 at 00:14
  • @chqrlie: The comment before the statement says that the code avoids branching; the tail comment on the same line as the statement says the result is -1 if v is negative and 0 otherwise — and that was the comment I was referring to. Neither comment discusses the casting. – Jonathan Leffler Nov 24 '15 at 00:44
  • 5
    With `double v = -2.0;` , then `(unsigned int)v` causes undefined behaviour, but `(unsigned int)(int)v` is well-defined. Also, with `long long v = LLONG_MAX`, then `(unsigned int)v` is well-defined but `(unsigned int)(int)v` causes implementation-defined behaviour which may raise a signal. Another differing case is `unsigned short v = USHRT_MAX` on 16-bit int systems. This could all be moot though, as chux has pointed out the code is only meant to work with `int v;` in which case casting `v` to int is of course redundant. – M.M Nov 24 '15 at 02:18
  • 4
    @M.M but here v is an `int` – phuclv Nov 24 '15 at 02:47
20

Quoting the C Standard 6.5.7p5:

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

The author is writing on how to implement a function sign(int v) that returns -1 for negative numbers and 0 for 0 and positive numbers efficiently. A naive approach is this:

int sign(int v) {
    if (v < 0)
        return -1;
    else
        return 0;
}

But this solution may compile to code that performs a comparison and branch on CPU flags set by the comparison. This is inefficient. He proposes a simpler and more direct solution:

sign = -(v > 0);

But this computation still requires compare and branch on CPUs that do not produce comparison results directly as boolean values. CPUs with flag registers typically set various flags upon comparison instructions or even most arithmetic instruction. So he proposes another solution based on shifting the sign bit, but as the Standard specifies above, he cannot rely on the result of right shifting a negative value.

Casting v as unsigned removes this problem because right shifting unsigned values is well specified. Assuming the sign bit is in the highest position, which is true for all modern processors but not mandated by the C standard, right shifting (unsigned)v by the one less than the number of bits in its type produces a value of 1 for negative values and 0 otherwise. Negating the result should produce the expected values -1 for negative v and 0 for positive and zero v. But the expression is unsigned, so plain negation will produce UINT_MAX or 0, which in turn causes arithmetic overflow when stored to an int or even just cast as an (int). Casting this result back to int before negating it correctly computes the desired result, -1 for negative v and 0 for positive or zero v.

Arithmetic overflow is usually benign and widely ignored by most programmers, but modern compilers tend to take advantage of its undefinedness to perform aggressive optimizations, so it is unwise to rely on expected but unwarranted behavior and best to avoid arithmetic overflow in all cases.

The expression could be simplified as:

sign = -(int)((unsigned)v >> (sizeof(int) * CHAR_BIT - 1));

Note that if right shifting is defined as replicating the bit for your platform (an almost universal behavior with current CPUs), the expression would be much simpler (assuming int v):

sign = v >> (sizeof(v) * CHAR_BIT - 1));   // works on x86 CPUs

The bithacks page https://graphics.stanford.edu/~seander/bithacks.html , very instructive indeed, contains a detailed explanation:

int v;      // we want to find the sign of v
int sign;   // the result goes here 

// CHAR_BIT is the number of bits per byte (normally 8).
sign = -(v < 0);  // if v < 0 then -1, else 0. 
// or, to avoid branching on CPUs with flag registers (IA32):
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));
// or, for one less instruction (but not portable):
sign = v >> (sizeof(int) * CHAR_BIT - 1); 

The last expression above evaluates to sign = v >> 31 for 32-bit integers. This is one operation faster than the obvious way, sign = -(v < 0). This trick works because when signed integers are shifted right, the value of the far left bit is copied to the other bits. The far left bit is 1 when the value is negative and 0 otherwise; all 1 bits gives -1. Unfortunately, this behavior is architecture-specific.

As an epilogue, I would recommend to use the most readable version and rely on the compiler to produce the most efficient code:

sign = -(v < 0);

As can be verified on this enlightening page: http://gcc.godbolt.org/# compiling the above code with gcc -O3 -std=c99 -m64 indeed produces the code below for all solutions above, even the most naive if/else statement:

sign(int):
    movl    %edi, %eax
    sarl    $31, %eax
    ret
Nan Xiao
  • 16,671
  • 18
  • 103
  • 164
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • I still don't understand why v >> (sizeof(int) * CHAR_BIT - 1) should be casted back to int. Why this may cause a integer overflow? – nalzok Nov 24 '15 at 02:00
  • Let's decompose the steps and assume 32 bits for simplicity. If `v` is negative, `v >> 31` is implementation defined, so we should not use it. `(unsigned)v >> 31` has value `1` for negative `v` and `0` for positive. But it is an unsigned expression, so `-((unsigned)v >> 31)` has value 0xFFFFFFFF, which does not fit in an `int`: casting this to `int` or simply storing it to an `int` variable invokes undefined behavior. In current implementations, it is usually not a problem, but for portability's sake, casting to `(int)` before negating is required. – chqrlie Nov 24 '15 at 12:16
  • @chqrlie: Casting UINT_MAX to an "int" is not allowed to invoke Undefined Behavior. An implementation may specify that it invokes an Implementation-Defined signal, or may specify that it yields -1, or could if desired specify that it yields some other particular value (e.g. 8675309) but the implementation must either specify a value that it will return or specify that it will raise a signal. Neither case is UB. – supercat May 05 '16 at 18:43
  • @supercat: you are correct, *undefined behavior* was not the correct term, but the explanation still holds, casting as `(int)` before negating is required to avoid the implementation defined behavior. IMHO specifying 2s complement and getting rid of all this counter-intuitive obsolete crap would greatly simplify the language and prevent obscure bugs caused by over-optimizations in advanced compilers that take unfair advantage from subtile cases of undefined behavior in innocent-looking programs. – chqrlie May 05 '16 at 21:20
  • @chqrlie: More generally, given that compilers are seldom provided by the same entities that provide the platforms upon which code will run, and that in many cases a programmer using a compiler will know things about the platform which the compiler writer doesn't, the Standard should recognize that the vast majority of C implementations will have two parts, and distinguish between "compiler UB" and "platform UB", such that a programmer who knows that a platform supports a behavior in a situation not mandated by the Standard would be entitled to exploit that. – supercat May 05 '16 at 21:56
  • @chqrlie: The current drafts of the next standard introduce a concept of "pointer provenance" which may be fine for "sandboxing" implementations of a language which is like C but with certain unsafe constructs curtailed, but would be totally unworkable in cases where a platform might, at run-time, allocate a range of address space and make it available to a program via means the Standard does not recognize and a compiler may know nothing about. The draft standard suggests that a linker spec could identify what addresses should be acceptable, but... – supercat May 05 '16 at 22:00
  • ...in some cases the addresses wouldn't be known until after the program is already running. – supercat May 05 '16 at 22:03
  • @supercat: that's a different issue. Trying to specify a sandbox for C seems interesting but difficult. I was referring to compilers eliding code like `if (i < i + 1)` when `i` is an `int`. – chqrlie May 05 '16 at 22:05
  • @chqrlie: That's actually an optimization I don't mind, provided that `if (i < (int)(i+1))` wraps the sum into the range of "int". Hyper-modernism, however, suggests that compilers should eagerly break things like `unsigned mul(unsigned short x, unsigned short y) { return x*y; }` [assume short==16 int==32] if they notice x*y might be in the range INT_MAX+1u to UINT_MAX. The authors of the Standard may have expected that such code would work on modern platforms, but that doesn't mean the authors of gcc think it should. – supercat May 05 '16 at 22:30
10

It is first casting to int, then to unsigned int, then it's performing the shift, then it's casting back to int and finally it's negating the result and storing it in sign. The unsigned cast is the one that could affect the result, since it will force a logical shift (which will zero-fill), as opposed to an arithmetic shift (which will sign extend).

Note that they actually want an arithmetic shift, but I don't believe C guarantees its availability, which is presumably why they're manually performing the negation of the logically shifted sign bit.

Tom Karzes
  • 22,815
  • 2
  • 22
  • 41
  • Shifting a negative number is implementation defined. As far as I'm aware, C doesn't require two's complement, so it wouldn't be able to guarantee the availability of arithmetic shift. related: http://stackoverflow.com/questions/12276957/are-there-any-non-twos-complement-implementations-of-c – Bobby Sacamano Nov 23 '15 at 23:58
  • 1
    two's complement does not imply arithmetic shift, right shifting negative values is implementation defined period. – chqrlie Nov 24 '15 at 00:18