70

Could someone explain the rationale, why in a bunch of most popular languages (see note below) comparison operators (==, !=, <, >, <=, >=) have higher priority than bitwise operators (&, |, ^, ~)?

I don't think I've ever encountered a use where this precedence would be natural. It's always stuff like:

  if( (x & MASK) == CORRECT ) ...   // Chosen bits are in correct setting, rest unimportant

  if( (x ^ x_prev) == SET )      // only, and exactly SET bit changed

  if( (x & REQUIRED) < REQUIRED )   // Not all conditions satisfied

The cases where I'd use:

  flags = ( x == 6 | 2 );     // set bit 0 when x is 6, bit 1 always.

are near to nonexistent.

What was the motivation of language designers to decide upon such precedence of operators?


For example, all but SQL at the top 12 languages are like that on Programming Language Popularity list at langpop.com: C, Java, C++, PHP, JavaScript, Python, C#, Perl, SQL, Ruby, Shell, Visual Basic.

gnat
  • 21,442
  • 29
  • 112
  • 288
SF.
  • 5,078
  • 2
  • 24
  • 36
  • 17
    mistake in the original design back in C – ratchet freak Apr 11 '13 at 08:43
  • 2
    Could you please explain in more details what language are you talking about? Not all of them fit, see: [Which programming languages doesn't use operator precedence besides Lisp like languages?](http://programmers.stackexchange.com/q/3425/31260) "bunch of most popular" somehow doesn't quite cut it – gnat Apr 11 '13 at 08:45
  • 7
    @gnat, what is the point of that complaint? The OP didn't say "all", just "a bunch of the most popular languages". And the vast majority follow this order. In this table, only one of the top 12 (SQL) doesn't: http://www.langpop.com/ –  Apr 11 '13 at 08:59
  • 3
    @dan1111 point is, naturally, to help answerers better understand the question asked and provide better answers. You see, this is not a place for [The Guessing Game](http://blog.stackoverflow.com/2012/02/lets-play-the-guessing-game/ "what's this") - or, as [about] page says, "It's not a discussion forum. There's no chit-chat." – gnat Apr 11 '13 at 09:06
  • @Gnat: I guess, following dan1111's comment, my answer is no longer required? – SF. Apr 11 '13 at 11:16
  • 7
    @gnat, I agree with your concern about guessing games, but I don't think this qualifies when nearly every popular language exhibits the described behavior. –  Apr 11 '13 at 11:18
  • Even if the precedence rules were how you prefer them, I imagine that most programmers who want to write readable code would still end up putting the parentheses exactly as they are in the current rules. – Dunk Apr 11 '13 at 18:51
  • 6
    @Dunk: The common "by hunch" approach is `[arithmetics] [logic operator] [arithmetics]`. Most programmers don't create a mess of parentheses like `if(((x+getLowX()) < getMinX) || ((x-getHighX())>getMaxX())))` - most will assume precedence of arithmetics over logics and write `if( ( x + getLowX() < getMinX ) || ( x - getHighX() > getMaxX() ))` assuming precedence of `+` above `<`. Now intuitively `if( x ^ getMask() != PATTERN )` should behave the same, XOR being arithmetic operator. The fact it's interpreted as `if( x ^ ( getMask() != PATTERN ) )` is completely counter-intuitive. – SF. Apr 12 '13 at 08:03
  • 1
    @SF - I couldn't disagree more. Most developers are more concerned with solving the problems at hand instead of memorizing useless and frequently language specific trivia such as operator precedence when there is a clear way to make the order of processing explicit. If you are creating "a mess of parentheses" then it makes more sense to simplify rather than assume that someone is going to memorize the precedence of every single possible operator. – Dunk Apr 12 '13 at 13:52
  • 1
    @Dunk: I don't know about you but I remember multiplication/division/AND has priority over addition/substraction/OR, and remembering "arithmetic above logic" is exactly one bit of information. Besides, there's a chart of operator precedence hanging on the wall by me, just in case - and the only simplification the "mess" example needs to be clear is removal of the redundant parentheses. And as for "concerned with solving problem at hand" - neglecting code clarity at that phase tends to bite you in the back while maintaining the code. – SF. Apr 12 '13 at 14:00
  • 2
    @SF - Your point of "there's a chart on the wall" proves my point exactly. I shouldn't have to go looking at a chart. Additionally, while in maintenance I shouldn't miss an obvious error staring me in the face because I missed that the code is using the wrong operator precedence. Hiding the order of processing is definately not improving "code clarity". Also, if you think your example code is well written and can't be simplified....LOL....what the heck is x+getLowX? – Dunk Apr 12 '13 at 14:26
  • 3
    @Dunk, your counterargument makes no sense. I've *never* met a programmer that doesn't know that arithmetic operators have precedence over comparisons. That's really basic stuff. The fact that bitwise operators--which are arithmetical in nature--don't follow the same rules of precedence as arithmetic operators is completely counter-intuitive. To say "nobody remembers all the precedence rules" is a non-argument. I don't know them all. And I don't have an operator precedence table posted on my wall. But I *do* know that arithmetic operations come before comparisons, as everyone else knows. – Ben Lee Apr 15 '13 at 20:25
  • @Ben - My arguments make perfect sense. You just don't agree with them. Big difference. So what "if every programmer knows", it still doesn't change the fact that in many cases, relying on precedence is bound to cause a person reading the code to miss otherwise obvious errors. Also, if the only comparisons that are done use the obvious arithmetic/comparison operators "that everyone else knows" then your point may have some merit. However, there are boatloads of operator precedence situations that include more than that, so your point is pretty meaningless. – Dunk Apr 15 '13 at 21:38
  • 1
    @Dunk, when I said "your counterargument makes no sense" I didn't just mean "I disagree". I meant "it's an argument that doesn't actually attempt to counter the point that SF was making". I actually mostly agree with what you said, but really don't think it makes sense as a counter-argument. I'll leave it at that, because I believe we just have different viewpoints and escalating the argument will not be constructive for anyone (of course, if you want to have a last word, go for it, but I'm leaving it at this). – Ben Lee Apr 15 '13 at 21:44
  • 1
    Sorry, not understand your point about `Python` in 12 languages. In python bitwise operators have higher priority than comparisons: https://maketips.net/tip/73/python-operator-precedence-priority, so if you do `if x & MASK == CORRECT:`, it will be the same as `if (x & MASK) == CORRECT:`, or I missed something? – Ivan Borshchov Dec 10 '16 at 21:53

2 Answers2

76

Languages have copied that from C, and for C, Dennis Ritchie explains that initially, in B (and perhaps early C), there was only one form & which depending on the context did a bitwise and or a logical one. Later, each function got its operator: & for the bitwise one and && for for logical one. Then he continues

Their tardy introduction explains an infelicity of C's precedence rules. In B one writes

if (a == b & c) ...

to check whether a equals b and c is non-zero; in such a conditional expression it is better that & have lower precedence than ==. In converting from B to C, one wants to replace & by && in such a statement; to make the conversion less painful, we decided to keep the precedence of the & operator the same relative to ==, and merely split the precedence of && slightly from &. Today, it seems that it would have been preferable to move the relative precedences of & and ==, and thereby simplify a common C idiom: to test a masked value against another value, one must write

if ((a & mask) == b) ...

where the inner parentheses are required but easily forgotten.

AProgrammer
  • 10,404
  • 1
  • 30
  • 45
  • 1
    Wouldn't that fail if `c=2` Or did `a==b` result in `~0` and not `1`? – SF. Apr 11 '13 at 11:27
  • Seem so, a==b returns 0 or 1, see http://cm.bell-labs.com/cm/cs/who/dmr/kbman.html. – AProgrammer Apr 11 '13 at 11:35
  • Check the reference in the answer and read the preceding text. – SShaheen Apr 11 '13 at 20:14
  • Oh. So the meaning of & changes depending on where you place it. For `a=1; b=2;` if(a & b){...}` will execute the block, while `c = a & b; if(c){...}` will consider the condition not satisfied. Big thanks to mr. Richie for fixing that. – SF. Apr 12 '13 at 08:11
  • Interesting. Having only one operator whose meaning is contextual is really the best way to do it. (integer and integer: bitwise and, boolean and boolean: boolean and; integer and boolean: syntax error). Problem is, C doesn't have a proper boolean type (everything can be a boolean) so this system doesn't work and we're left with the ugly hack of having duplicated operators. And even later C-derived languages that do have a real boolean type don't fix this problem. (Looking at you, C# and Java.) – Mason Wheeler May 06 '13 at 15:39
  • @MasonWheeler, the meaning wasn't type dependent (BCPL and B were an untyped languages like BLISS and assembly languages, TCL is also an untyped language, but using strings instead of words), it was dependent on the context (in if() it was logical, in assignation it was bitwise). – AProgrammer May 06 '13 at 15:46
  • 1
    @MasonWheeler: There are cases where one needs to use `&&` with Boolean values to force short-circuiting, or `&` to prevent it. Further, if `x && y` had been defined as `x ? y : 0`, and `x || y` as `x ? x : y` [but with the latter only evaluating `x` once], they could have been useful operations on numbers (which--incidentally--could likely have allowed faster faster execution than the way they're actually defined). Having separate operators is IMHO a *good* thing, even though I don't like the way they're defined with integers. – supercat Jul 08 '14 at 18:50
  • @Supercat: I would argue that there's no valid case for forcing non-short-circuit evaluation that cannot be better handled (ie: easier for someone who is not the original author to comprehend the intent of the code when reading it) by expanding it to explicit control flow. – Mason Wheeler Jul 08 '14 at 19:19
  • @MasonWheeler: If expressions could declare and use temporary values, I might agree with you; since they cannot, there are times when an expression rather than a statement is required and there would be no good place to declare a temporary variable. – supercat Jul 08 '14 at 19:55
  • @supercat: What's wrong with declaring it the same as any other variable? That's the entire point of making things explicit: *making them explicit.* – Mason Wheeler Jul 08 '14 at 20:05
  • 1
    @MasonWheeler: Especially in embedded systems, it's sometimes necessary to use function-like macros [in some cases, they can offer orders-of-magnitude better performance than in-line functions, and in some embedded systems that can be critical]. Variable declaration within such macros is not possible; using a global variable within a macro might work, but seems really icky. – supercat Jul 08 '14 at 20:12
  • @supercat: Forgive my skepticism, but in today's modern world, systems like Arduino and Raspberry Pi with enough hardware to play back movies in full HD for an ultra-low cost are the reality. "Embedded systems with extreme hardware constraints that require horrible coding practice" just isn't a valid excuse in 2014 IMO. – Mason Wheeler Jul 08 '14 at 20:17
  • 7
    @MasonWheeler: Can you find a Raspberry Pi or Arduino for $0.50 in quantity 1000? – supercat Jul 08 '14 at 20:20
  • @supercat: If I need `x ? y : 0` I write `x ? y : 0`. OTOH, I frequently write `status = (threshold > thresh_max)*MASK_MAX_EXCEEDED + (threshold < thresh_min)*MASK_MIN_EXCEEDED + (!!alarm)*MASK_ALARM + (motor_out!=motor_in)*MASK_MOTOR_ERROR;` etc. The fact boolean expressions produce 0 or 1 only is very comfortable, allowing to turn many conditionals into simple multiplications (which additionally avoid branches in pipeline, meaning better efficiency.) – SF. Nov 03 '14 at 11:55
  • 1
    @SF.: How often do you use the short-cut operators in contexts where you need the result to be 0 or 1, but the operands might have other values? The fact that comparison operators always yield 0 or 1 is useful, but the fact that `&&` and `||` coerce all non-zero values to 1 is far less so. – supercat Feb 06 '18 at 21:42
  • I didn't pay attention to it enough to notice - I guess not as frequently but sometimes. Generally, I relatively frequently use the idiom of `status = (boolean expression 1) * (bit flag 1) + (boolean expression 2) * (bit flag 2) + ...` and if the boolean expression involves `||` or `&&` I can skip casting the value into bool through, e.g. `!!`. So it's convenient, if not very important. – SF. Feb 07 '18 at 07:17
  • OTOH, I avoid purposeful short-circuiting that does more than save some CPU cycles; it's asking for trouble in the long run - easily overlooked and leading to hard to find bugs if the code is changed later and whoever changes it doesn't notice given piece *needs* to be short-circuited. Similarly, instead of jumping through hoops to avoid macros that evaluate the parameter more than once, I just make sure whatever expression I pass to the macro (or things that have a potential to be macros) has no side effects. There's absolutely no benefit calling `function(x++);` over `function(x) ; x++;`. – SF. Feb 07 '18 at 07:21
7

Bitwise operators are related to logical operators both conceptually and in appearance, which probably explains why they are near each other in the precedence table. Perhaps one could even argue that it would be confusing for & to be higher than ==, yet have && be lower then ==.

Once a precedence precedent (!) was set, it was probably better for other languages to follow it for consistency's sake.

However, I tend to agree with you that this is not optimal. In actual use, bit operators are more like mathematical operators than logical ones, and it would be better if they were grouped with the mathematical operators in precedence.