6

Absolutely academic context question-

I found countless articles listing the order of operator precedence in all languages, but what is the logical reasoning behind that specific order?

For clarity of answers, let's talk about the C/C++/C# precedence table, even though it is pretty much the same in all other languages.

1
  • 1
    It's essentially the same order of operations that you learned in grade school, that's all. Some decisions (like && and || precedence) were made to reduce the number of parentheses. Commented May 2, 2019 at 15:16

2 Answers 2

17

The designers of the Fortress Programming Language did extensive reviews of scientific literature to see how operators are used and what relative precedence they have. Based on those observations, they designed operator precedence in Fortress. This is the only instance I know of where a Programming Language Designer has explicitly written down their rationale for Operator Precedence.

Operator Precedence in Fortress works a little bit different from other languages. The main difference is that there is no globally defined precedence across all operators. Instead, operators have relative precedence to other operators: * is-higher-than +, for example. Using two operators in the same expression without explicit parentheses that do not have a defined operator precedence, is a Syntax Error. In other words: the precedence relation is not a total ordering, it is in fact not even a partial ordering, it isn't an ordering at all because the relationship is not transitive: even if operator X is defined to be tighter than Y and Y is defined to be tighter than Z, that does not mean that X is tighter than Z and you are not allowed to mix X and Z in the same expression without parentheses because they do not have a defined relative precedence.

This neatly side-steps questions such as "should the string concatenation operator have higher or lower precedence than bitwise-XOR?" Well, neither. It doesn't make sense to mix the two in the same expression, so they don't have a defined relative precedence. And if you do mix the two, then you need to use parentheses.

In general, operators in Fortress only have a defined relative precedence, if they are commonly used together in the same expression in scientific papers and there is a general consensus in the scientific community what the precedence should be.

There is also another nice feature: the programmer can use whitespace to indicate what he thinks the precedence should be (but not override precedence). So, you can write a×b+c, you can write a × b + c, you can write a×b + c, but you cannot write a × b+c, the latter is again a Syntax Error. (And yes, Fortress uses the times symbol for multiplication, not the asterisk. It also uses ∧ and ∨ for logical-conjunction and logical-disjunction, ∪ and ∩ for union and intersection, it uses actual subscripting for array subscripting and uses superscript for exponentiation.)

A third nice feature is that Fortress has a juxtaposition operator, i.e. an operator for writing two expressions next to each other. For example, for numbers, juxtaposition is defined as multiplication, i.e. in the example above, I could have written a b + c instead of a × b + c. For unary functions, juxtaposition is application, so f(x) and f x are equivalent.

10

There isn't some grand logical scheme, especially in C and the derivatives that kept its order. It's basically a combination of "this feels right" and historical accident.

Obviously, you want to keep the precedence of arithmetic operations that you learned in school: * and / before + and -. All the number manipulation should be somewhere in that area. (Except the bitwise logical operators, I'll get there.)

Next, you want comparison operators. The reason is that interpreting x + y == z as x + (y == z) is simply rarely what you want, even though it's legal in C and C++. (Not in C# or Java.) Generally you first calculate some term, and then compare it to something else. You don't generally compare something and then take the result of that comparison as a numerical value for other calculations.

Finally, you want logical operators. Again, this is simply based on intuition and usefulness. x == y && a == b is not very useful as x == (y && a) == b. && has precedence over || because that's the way it is done in the mathematical foundations.

So what about those bitwise logical operations? Well, early C didn't have the logical short-circuiting && and ||. & and | were the logical operators (and also did double-duty as the bitwise operators). Since C didn't have a boolean datatype either but just used 0 and 1, they did that job just fine, except for that pesky precedence thing. (Also, C was meant to closely reflect the underlying machine operations, and the underlying machine didn't distinguish logical and bitwise AND and OR either.) Thus, you find the bitwise operators way down in the precedence with the logical operators, and that's why you have to write (flags & mask) == expected.

So, historical accident. Some newer languages break from C in the precedence of the bitwise operations.

Similar reasoning can be applied to the unary operators.

1
  • 2
    I would say the grand scheme is: be consistent with what mathematicians did already for a long time. Commented May 2, 2019 at 6:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.