45

When I compile this code with VC++10:

DWORD ran = rand();
return ran / 4096;

I get this disassembly:

299: {
300:    DWORD ran = rand();
  00403940  call        dword ptr [__imp__rand (4050C0h)]  
301:    return ran / 4096;
  00403946  shr         eax,0Ch  
302: }
  00403949  ret

which is clean and concise and replaced a division by a power of two with a logical right shift.

Yet when I compile this code:

int ran = rand();
return ran / 4096;

I get this disassembly:

299: {
300:    int ran = rand();
  00403940  call        dword ptr [__imp__rand (4050C0h)]  
301:    return ran / 4096;
  00403946  cdq  
  00403947  and         edx,0FFFh  
  0040394D  add         eax,edx  
  0040394F  sar         eax,0Ch  
302: }
  00403952  ret

that performs some manipulations before doing a right arithmetic shift.

What's the need for those extra manipulations? Why is an arithmetic shift not enough?

phuclv
  • 37,963
  • 15
  • 156
  • 475
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 3
    FWIW, in C89 and C++03 it was implementation-defined which way integer division rounds for negative operands. In C99 and C++11 it is not. – Steve Jessop Oct 02 '12 at 14:26
  • @AlexeyFrunze This one's actually pretty good compared to some of the other things that get massively upvoted. So I don't see it as undeserved. – Mysticial Oct 02 '12 at 15:39
  • 1
    possible duplicate of [Examining code generated by the Visual Studio C++ compiler, part 1](http://stackoverflow.com/questions/1658186/examining-code-generated-by-the-visual-studio-c-compiler-part-1) – fredoverflow Oct 02 '12 at 15:42
  • 1
    @Mysticial I thought that every C(++)/low-level programmer worth their salt either would already know it or would be able to figure it out. Or the upvotes are coming from those who don't know this yet. I might be wrong. – Alexey Frunze Oct 02 '12 at 15:45
  • 1
    @FredOverflow That does appear to be a dupe. But this one has a better title and is more straight to the point. I'm not sure what the policy is for closing a higher quality question as a dupe of a lower quality one. – Mysticial Oct 02 '12 at 15:48
  • 4
    @Mysticial Well, we could also vote to close the old question as a dupe of the new one... – fredoverflow Oct 02 '12 at 15:50
  • @SteveJessop Although even when rounding used to be implementation-defined, “rounding towards zero when the divisor is dynamic, and downwards for some values of static divisors” would probably not be acceptable. This reminds me of a question I have about glibc's `strtof()`, which tries to round to nearest-even but fails for some borderline decimal values. I wondered if that counted as “rounding to 1ULP in an implementation-defined manner”. Should I ask that question? – Pascal Cuoq Oct 02 '12 at 16:09
  • @PascalCuoq: I don't think the integer behavior you describe would conform, I think the intent of the standard is that it's always the same direction. Otherwise it would probably just say that it's unspecified. I was only thinking that if the implementation wanted to optimize signed division by 2 as a shift, then in C89 and C++03 it could do so provided it rounded to negative infinity for division in general. – Steve Jessop Oct 02 '12 at 16:26
  • "an implementation-defined manner" is a bit different from "the sign of the remainder is implementation-defined", so maybe the float behavior you describe is OK. I think there's some ambiguity, though, what implementation-defined behavior is allowed to depend on. Clearly the size of an `int` is a fixed quantity, it can't depend on the phase of the moon at runtime, but for example can the sign of a modulus depend on *which* of the two operands is negative? I don't know. – Steve Jessop Oct 02 '12 at 16:28
  • 2
    This is a good example for when people say not to shift when you mean divide because the compiler knows that optimization. Turns out the compiler also knows when it's "OK". – phkahler Oct 02 '12 at 17:19
  • 1
    @AlexeyFrunze So, ahem, do you remember that day when you were typing "Hello world" into the code editor?... – Alex B Oct 15 '12 at 11:52
  • @AlexB No, "Hello World" per se was of little significance. I don't remember what text it was in the first program some 22+ years ago. It could very well be something in Russian using Latin letters. But I digress. I've known about the sign-preserving arithmetic right shift for some 14+ years now. Not sure how I first learned it, but I sure was beaten by it and even wrote a small article for a friend about avoiding the nasty effects of it in a computer graphics algorithm. It could be that I learned about it 17 years ago when I ported a floating-point library before graduating from highschool.Y? – Alexey Frunze Oct 15 '12 at 12:56
  • 1
    @AlexeyFrunze My point was that everyone has to start somewhere, and SO accommodates people of all skill and experience ranges, so you don't have to be condescending to people who write questions you consider basic. – Alex B Oct 15 '12 at 23:42

3 Answers3

91

The reason is that unsigned division by 2^n can be implemented very simply, whereas signed division is somewhat more complex.

unsigned int u;
int v;

u / 4096 is equivalent to u >> 12 for all possible values of u.

v / 4096 is NOT equivalent to v >> 12 - it breaks down when v < 0, as the rounding direction is different for shifting versus division when negative numbers are involved.

Paul R
  • 208,748
  • 37
  • 389
  • 560
35

the "extra manipulations" compensate for the fact that arithmetic right-shift rounds the result toward negative infinity, whereas division rounds the result towards zero.

For example, -1 >> 1 is -1, whereas -1/2 is 0.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
11

From the C standard:

When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.105) If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a; otherwise, the behavior of both a/b and a%b is undefined.

It's not hard to think of examples where negative values for a don't follow this rule with pure arithmetic shift. E.g.

(-8191) / 4096 -> -1
(-8191) % 4096 -> -4095

which satisfies the equation, whereas

(-8191) >> 12 -> -2 (assuming arithmetic shifting)

is not division with truncation, and therefore -2 * 4096 - 4095 is most certainly not equal to -8191.

Note that shifting of negative numbers is actually implementation-defined, so the C expression (-8191) >> 12 does not have a generally correct result as per the standard.

pmdj
  • 22,018
  • 3
  • 52
  • 103