10
$\begingroup$

In C, the relative precedence of bitwise operators is as follows, from high to low precedence:

~       bitwise NOT
<< >>   bitwise left shift and right shift
&       bitwise AND
^       bitwise XOR
|       bitwise OR

It makes sense that ~ have a higher precedence than & which itself have a higher precedence than |, by analogy to the precedence of logical operators.

What I'm wondering is if the precedence of bitwise XOR ^ is right or if maybe it should be on the same level as bitwise OR |?

Additionally, I'm wondering what is the rationale for having the precedence of the bitwise shift operators << and >> where it is and if it corresponds to their regular use cases, specifically in relation with bitwise AND &? If it is in an awkward position, where should it be ideally?

$\endgroup$
6
  • 1
    $\begingroup$ Are you designing a new language that uses these same operators? $\endgroup$ Commented Mar 25 at 17:33
  • $\begingroup$ @EricLippert I'm trying to design a language that have these operators period, yes. I don't understand your comment, having these operators is the norm in every imperative and scripting languages. $\endgroup$
    – WhiteMist
    Commented Mar 25 at 17:44
  • 2
    $\begingroup$ These are common choices; I would encourage you to look at more languages if you genuinely think these are the only choices, or the best choices. My question was intended to clarify whether you were asking about historical defects in the design of C that we're now aware of with the benefit of hindsight, or if you were asking to inform the design of a new language. $\endgroup$ Commented Mar 25 at 20:07
  • $\begingroup$ There's also the possibility of incomparable, non-transitive, precedences, as in Fortress and some other language I don't remember ATM. (See also "Fortress Features and Lessons Learned".) $\endgroup$
    – Pablo H
    Commented Mar 25 at 20:13
  • 1
    $\begingroup$ To me it is logical that the bitwise shift operators have high precedence as they are sort of 'equivalent/most similar' to multiplication/exponentiation. And bitwise OR is the most similar to addition and has the lowest. $\endgroup$
    – CPlus
    Commented Mar 26 at 14:09

3 Answers 3

5
$\begingroup$

The precedence should be the same as it is in C, for several reasons.

C got it right

C got the relative precedences for these operators right*, so there's nothing to fix by changing it:

  • ~ has the highest precedence, by analogy with logical operators. It's also very common to use ~ to invert a mask that you want to apply with &, so ~ should have higher precedence than &.
  • Shifts have the next-highest because usually one or both sides are literals or other fixed constants, i.e. things which require no bitwise operations to compute. Shifts are also analogous to exponentiation (1 << n equals 2 ** n) which has a high precedence in arithmetic.
  • & has lower precedence than shifts because often you want to shift something to the correct position, and then use & to keep just some of the bits, like x >> offset & mask. If the mask is something like 0xFF then you can clearly see that the result will only have the lowest 8 bits; this is more readable than (x & mask) >> offset because the mask no longer shows which bits might be set in the result.
  • ^ and | have lower precedence than & both by analogy to logical operators, and because it's common to use them to combine the results of multiple shifts or masked-shifts. The relative precedence of ^ and | doesn't have much practical importance because there are few reasons to use them together.

*By "right", I mean C avoids requiring parentheses as much as possible for the most common uses of bitwise operators, and also matches the relative precedences of analogous operators in mathematics. Of course there's some room for debate over which uses are most common and which mathematical operators are more analogous, but no design rationale can be perfectly objective.

Compatibility with C is important

There are a lot of clever things you can do with bitwise operators, but most programmers will not invent their own new clever things. If people use bitwise operators, it's mostly in code they copy-paste from Stack Overflow, or popular articles such as this one.

These sources will invariably assume C's precedence rules for bitwise operators, either because they are written in C, or because they are written in other languages with the same precedence rules for bitwise operators (and that's every other popular language with bitwise operators, as far as I know).

Therefore, your users' interests are best served by not adding an extra step that must be taken when translating such code into your language, particularly an extra step which users don't necessarily know they need to take.

Users won't learn the precedences

For bitwise operators (and sometimes even boolean logical operators!) it's often recommended to use parentheses to clarify the order of operations, even when parentheses would be redundant. Because even if you know the bitwise operator precedences, most of the people who will read your code do not.

The vast majority of programmers do not use these operators very often, and will not spend the effort to memorise their precedences. (This is especially the case for the relative precedences of bitwise and non-bitwise operators, like << and +.) So, if people will use brackets anyway, you don't gain anything by giving them different precedences. Put another way, this isn't an efficient use of your language's strangeness budget.

$\endgroup$
6
  • 1
    $\begingroup$ This is exactly the answer I was looking for, thank you. Now, everything is not right in the C precedence table, the biggest default being that the level of = is above &, ^ and | as I'm sure you know, but you've convinced me that the relative precedence of the bitwise operators should be kept as is. $\endgroup$
    – WhiteMist
    Commented Mar 25 at 23:38
  • $\begingroup$ A caveat about giving shifts higher precedence than AND is that it makes shift-then-mask easier to write, which in C unfortunately also makes it easier to get undefined or wrongly sign-extended results, than mask-then-shift; so if you are copying C’s precedence, it’s better not to copy its integer conversion rules. Another is that << and >>, being digraphs, are wider than &, which can make people misread them as having lower precedence—for much the same reason as beginners often expect a * b+c to mean a * (b + c). $\endgroup$
    – Jon Purdy
    Commented Mar 25 at 23:39
  • 1
    $\begingroup$ High precedence for shift is useful when you're shifting the mask, e.g. x | 1 << n is the standard idiom for setting bit n. $\endgroup$
    – Barmar
    Commented Mar 26 at 4:10
  • $\begingroup$ @Barmar That's an example of using | to combine the result of a shift with something else. If it's to update a bitset then |= is more common, though. $\endgroup$
    – kaya3
    Commented Mar 26 at 14:25
  • $\begingroup$ Good point about |=. The point of my comment is that this is less likely to run into overflow or bad sign extension. $\endgroup$
    – Barmar
    Commented Mar 26 at 15:15
2
$\begingroup$

If you want to minimize the need for parentheses

Let's typical use case of bitshift/masking operations.

Bit arrays

Suppose that you have a bit array class, allowing an integer (or array of integers) to be treated as an array of bits. It provides the operations:

  • get bitarray[i]if ((backing_int & (1 << i)) != 0)
  • set bitarrary[i] = valuebacking_int = (backing_int & ~(1 << i)) | (value << i)

To make these work without parentheses, you would need the precedence order:

  1. Shifts (<< and >>)
  2. NOT (~)
  3. AND (&)
  4. OR (|)
  5. Relationals (==, !=, etc.)

Note that I only used left shift (<<), not right shift (>>), but since these are parallel and inverse operators, similar to + and -, it would be weird to not have them at the same precedence level.

Also, I didn't include XOR (^) in my example. If you think of XOR as being a one-bit addition (with the carry bit ignored), then it makes sense to place it below AND (which is a one-bit multiplication). If you think of XOR as being a Boolean != operator, it makes sense to treat them at the same level as relationals — if you put relationals below OR, as I have done.

UTF-8 decoding

The following line of C(++) code decodes a 4-byte UTF-8 sequence to the corresponding Unicode code point (after the validity check has been done):

code_point = ((bytes[0] & 7) << 18) | ((bytes[1] & 0x3F) << 12) | ((bytes[2] & 0x3F) << 6) | (bytes[3] & 0x3F);

You could make the parentheses unnecessary by using the precedence order:

  1. AND (&)
  2. Shifts (<<, >>)
  3. OR (|)

Note that this switches the relative precedence of AND and shifting compared to the previous example.

If you want to minimize confusion

As @kaya3 pointed out, if you use the C syntax for operators, people will expect them to work the same as in C. Just as if you allow C-style ;, people will expect statement termination semantics to be the same as in C — to the confusion of JavaScript developers.

So, if you're committed to the symbols <<, >>, ~, &, |. and ^, it would probably be a good idea to keep C's precedence hierarchy for them.

If you decide to spell the operators differently (e.g., the word-like lshift, rshift, not, and, or, and xor), then this argument might not apply as much.

$\endgroup$
0
$\begingroup$

Just use function call syntax

In most kinds of programming, bitwise operators tend to be used very rarely, so most programmers are not going to remember their precedence rules. Given the rarity, what is even the point of having dedicated syntax for them? One could make them use the same syntax as regular function calls, where the precedence is unambigous:

def bitset_contains(bitset: int, item: int) -> bool:
    return bit_and(bitset, bit_left_shift(1, item)) != 0
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .