Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which operator(s) in C have wrong precedence?

In the "Introduction" section of K&R C (2E) there is this paragraph:

C, like any other language, has its blemishes. Some of the operators have the wrong precedence; ...

Which operators are these? How are their precedence wrong?

Is this one of these cases?

like image 288
Jay Avatar asked Feb 17 '19 13:02

Jay


4 Answers

There is a clear rule of precedence that is incontrovertible. The rule is so clear that for a strongly typed system (think Pascal) the wrong precedence would give clear unambiguous syntax errors at compile time. The problem with C is that since its type system is laissez faire the errors turn out to be more logical errors resulting in bugs rather than errors catch-able at compile time.

The Rule

Let ○ □ be two operators with type

○ : α × α → β
□ : β × β → γ
and α and γ are distinct types.

Then

x ○ y □ z can only mean (x ○ y) □ z, with type assignment
x: α, y : α, z : β

whereas x ○ (y □ z) would be a type error because ○ can only take an α whereas the right sub-expression can only produce a γ which is not α

Now lets

Apply this to C

For the most part C gets it right

(==) : number × number → boolean
(&&) : boolean × boolean → boolean

so && should be below == and it is so

Likewise

(+) : number × number → number
(==) : number × number → boolean

and so (+) must be above (==) which is once again correct

However in the case of bitwise operators

the &/| of two bit-patterns aka numbers produce a number ie
(&), (|) : number × number → number
(==) : number × number → boolean

And so a typical mask query eg. x & 0x777 == 0x777
can only make sense if (&) is treated as an arithmetic operator ie above (==)

C puts it below which in light of the above type rules is wrong

Of course Ive expressed the above in terms of math/type-inference

In more pragmatic C terms x & 0x777 == 0x777 naturally groups as x & (0x777 == 0x777) (in the absence of explicit parenthesis)

When can such a grouping have a legitimate use?
I (personally) dont believe there is any

IOW Dennis Ritchie's informal statement that these precedences are wrong can be given a more formal justification

like image 94
Rusi Avatar answered Nov 19 '22 16:11

Rusi


Yes, the situation discussed in the message you link to is the primary gripe with the precedence of operators in C.

Historically, C developed without &&. To perform a logical AND operation, people would use the bitwise AND, so a==b AND c==d would be expressed with a==b & c==d. To facilitate this, == had higher precedence than &. Although && was added to the language later, & was stuck with its precedence below ==.

In general, people might like to write expressions such as (x&y) == 1 much more often than x & (y==1). So it would be nicer if & had higher precedence than ==. Hence people are dissatisfied with this aspect of C operator precedence.

This applies generally to &, ^, and | having lower precedence than ==, !=, <, >, <=, and >=.

like image 28
Eric Postpischil Avatar answered Nov 19 '22 18:11

Eric Postpischil


Wrong may sound a bit too harsh. Normal people generally only care about the basic operators like +-*/^ and if those don't work like how they write in math, that may be called wrong. Fortunately those are "in order" in C (except power operator which doesn't exist)

However there are some other operators that might not work as many people expect. For example the bitwise operators have lower precedence than comparison operators, which was already mentioned by Eric Postpischil. That's less convenient but still not quite "wrong" because there wasn't any defined standard for them before. They've just been invented in the last century during the advent of computers

Another example is the shift operators << >> which have lower precedence than +-. Shifting is thought as multiplication and division, so people may expect that it should be at a higher level than +-. Writing x << a + b may make many people think that it's x*2a + b until they look at the precedence table. Besides (x << 2) + (x << 4) + (y << 6) is also less convenient than simple additions without parentheses. Golang is one of the languages that fixed this by putting <</>> at a higher precedence than + and -


In other languages there are many real examples of "wrong" precedence

  • One example is T-SQL where -100/-100*10 = 0
  • PHP with the wrong associativity of ternary operators
  • Excel with wrong precedence (lower than unary minus) and associativity (left-to-right instead of right-to-left) of ^:
    • According to Excel, 4^3^2 = (4^3)^2. Is this really the standard mathematical convention for the order of exponentiation?
    • Why does =-x^2+x for x=3 in Excel result in 12 instead of -6?
    • Why is it that Microsoft Excel says that 8^(-1^(-8^7))) = 8 instead of 1/8?
like image 41
phuclv Avatar answered Nov 19 '22 16:11

phuclv


It depends which precedence convention is considered "correct". There's no law of physics (or of the land) requiring precedence to be a certain way; it's evolved through practice over time.

In mathematics, operator precedence is usually taken as "BODMAS" (Brackets, Order, Division, Multiplication, Addition, Subtraction). Brackets come first and Subtraction comes last.Ordering Mathematical Operations | BODMAS Order of operations

Operator precedence in programming requires more rules as there are more operators, but you can distil out how it compares to BODMAS.

The ANSI C precedence scheme is pictured here: https://en.cppreference.com/w/c/language/operator_precedence

As you can see, Unary Addition and Subtraction are at level 2 - ABOVE Multiplication and Division in level 3. This can be confusing to a mathematician on a superficial reading, as can precedence around suffix/postfix increment and decrement.

To that extent, it is ALWAYS worth considering adding brackets in your mathematical code - even where syntactically unnecessary - to make sure to a HUMAN reader that your intention is clear. You lose nothing by doing it (although you might get flamed a bit by an uptight code reviewer, in which you can flame back about coding risk management). You might lose readability, but intention is always more important when debugging.

And yes, the link you provide is a good example. Countless expensive production errors have resulted from this.

like image 41
Bit Racketeer Avatar answered Nov 19 '22 16:11

Bit Racketeer