
From Basic to Intermediate: Operator Precedence
Introduction
The content presented here is intended solely for educational purposes. Under no circumstances should the application be viewed for any purpose other than to learn and master the concepts presented.
In the previous article "From Basic to Intermediate: FOR Statement", we covered the basics of the FOR statement. Using the material presented in earlier articles, you can create quite a good volume of MQL5 code. Even if these are quite simple applications, they can be a source of proud and enjoyment for many.
For many other programmers, small code snippets created by beginners can seem trivial, but if the beginner finds a solution to a problem encountered, this is really a reason to be proud. To be honest, what we've been discussing so far will only allow you to create some script-like code. Even if it is something very simple and requires no interaction, if you've done it yourself, then you have started applying the basic knowledge and are moving in the right direction.
But now it’s time to move on to a new topic. This topic will allow us to advance further in creating more meaningful code. Today we will explore operators. Although we have talked about them before, we are now going to take a few steps further because what we covered earlier was quite basic and simple. In this article, we will delve into operator precedence rules in practice, as well as the ternary operator which, for many, is a somewhat confusing concept, yet extremely useful in a vast range of situations. It can save us both time and effort in certain programming tasks.
The prerequisite for following along with the content in this article is an understanding of how to declare and use variables in MQL5 code. This topic has already been addressed in previous articles. If you do not yet have this knowledge, please refer to earlier articles before proceeding with this one. Let's now begin with the first topic of this article.
Precedence Rules
Understanding and being familiar with precedence rules is extremely important, and I've mentioned this in another article. But here we're going to dive a bit deeper into this topic.
When consulting the documentation for precedence rules, you'll find a table outlining them. However, many people fail to properly understand the meaning or information contained in the rules. If that's your case, dear reader, there's no need to feel embarrassed or hesitant. It's perfectly normal to feel somewhat confused when first encountering this kind of information. After all, in school, we don't typically learn things in the same way we apply them here, as programmers.
Although, in some cases, the way certain expressions are structured by some programmers might seem confusing at first glance, they are often technically correct. Even if the result appears unexpected to us. That's why it's so important to understand precedence rules. You don't need to memorize them. With continuous use and practice, you'll get used to them. But the most important thing is:
If you can't understand your own code, others certainly won't either.
That's why you should always try to think about how others might interpret what you're attempting to structure. So let's begin with the following point: the table mentioned at the beginning of this topic should be read from top to bottom. The operators listed at the top have higher execution priority. As you move down the table, the priority decreases progressively.
However, there is a small detail to keep in mind here. To explain this, let's take a look at the table in the figure below:
Figure 01
Here, you can see that the operators are listed in a specific order. Paying attention to this order is very important. You'll also notice that they're grouped into categories. This is where many people tend to get confused. That's because when we come across certain pieces of code, we can't predict the outcome of the expression if we don't understand the grouping shown in Figure 01. Fortunately, this grouping is actually quite simple to understand, and it makes things much easier so you don't need to memorize all the precedence rules. First, we have the referencing operators. These take precedence over all others, since they determine how we access a specific element. Right after that, we encounter type or binary operators. In these cases, the code should be read from right to left. But why is that? Don't worry, we'll get to that. I'll explain exactly how to read the code in such situations. For now, just note that this is different from referencing operators, which are read from left to right.
I know it sounds confusing. At this point, you might be thinking: "Why do they make this so complicated?" But this isn't complication for the sake of it, dear reader. In practice, it all makes perfect sense. Without seeing it in action, it might feel like you've entered a madhouse. But things become clearer in the third grouping, which includes elements more familiar to most people. These are the basic arithmetic operators, and in this case, code is read from left to right. This pattern continues down through the rest of Figure 01.
Now, let's see how all of this works in practice. For that, we'll use some very simple and straightforward code snippets, where we'll simply print some values. This is the fun and easy part.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char value = 9; 09. 10. PrintX(value); 11. PrintX(++value * 5); 12. PrintX(value); 13. } 14. //+------------------------------------------------------------------+
Code 01
So now I ask you, dear reader: what value will be printed in the terminal? Without a clear understanding of operator precedence, you might guess 46, since we're multiplying a variable (with a value of nine) by a constant (value five), and then adding one. But that would be WRONG. The actual result of running Code 01 is 50, as shown in the figure below:
Figure 02
Did that seem confusing? Well then, how about we try another little experiment, this time, changing just a small detail in the code. You can see it just below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char value = 9; 09. 10. PrintX(value); 11. PrintX(value++ * 5); 12. PrintX(value); 13. } 14. //+------------------------------------------------------------------+
Code 02
The result of code 02 is shown below:
Figure 03
Didn't I say in the previous article that we were going to have a lot of fun? I could keep playing around with these precedence rules for quite a while just to show you, dear reader, that at the very moment you think you've figured it all out and believe you're ready to take on the world, you've actually only just managed to stand up… and are about to take your very first step.
I know, you don't even need to tell me, that it all seems like madness. And you're probably thinking I must be some lunatic. But believe me, dear reader, the fun has only just begun. Things are going to get even more interesting. So, how about we look at an even more entertaining piece of code? Let's start with the one shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char v1 = 9, 09. v2 = 5; 10. 11. PrintX(v1); 12. PrintX(v2); 13. PrintX(v1 & 1 * v2); 14. PrintX((v1 & 1) * v2); 15. } 16. //+------------------------------------------------------------------+
Code 03
Beautiful code. It is truly delightful, especially when it's running inside a loop. Below is the output it produces.
Figure 04
Now admit it: you, just like me, are having fun seeing this sort of thing, aren't you? Notice how the result depends on the presence or absence of parentheses in an expression.
There's actually a general rule when it comes to writing this kind of expression. It's not a strict or formally documented rule, but it exists among programmers. And the rule goes like this:
When writing a complex expression, try to separate it into execution levels using parentheses. This makes it easier for other programmers to interpret.
In fact, even when the result is obvious and follows standard operator precedence, breaking things down into clearly defined layers using parentheses makes it much easier to understand what kind of result you expect. In some cases, not even the compiler can properly interpret what you're trying to do. Want to see an example of that? Well then, take a look at the code below. It's an example of a piece of code whose outcome is completely unpredictable, since even the compiler struggles to understand what should be done.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char v1 = 9, 09. v2 = 5; 10. 11. PrintX(v1); 12. PrintX(v2); 13. PrintX(v1++ & 1 * v2 << 1); 14. PrintX(v1); 15. PrintX(v2); 16. } 17. //+------------------------------------------------------------------+
Code 04
In this case, when you attempt to compile Code 04, the compiler will issue a warning shown below:
Figure 05
Notice that despite the warning, the compiler still generates the code. However, there's a significant risk here that the code may not produce the correct result in certain situations. Therefore, it would be unwise to trust this code blindly, especially when the compiler issues a warning indicating a potential flaw in the expression. In such situations, it is necessary to use parentheses. Without adding them and using only operator precedence rules, we can see whether the result would still be correct in this specific case.
To do this, we run the code, and the result is shown immediately below:
Figure 06
Here, the output was the value eight. But is that value actually correct? Well, to determine that, we need to manually break down how the expression was processed by the code. This kind of analysis is very common in programming. Contrary to what some may think, a programmer does not simply write code without knowing what the outcome should be. A good programmer ALWAYS knows what result their code should produce. NEVER write something without first understanding the intended outcome. In fact, a good programmer essentially performs a backtest of their own logic and then carries out what is known as a forward test, where each result is reviewed one by one. Only after a thorough round of testing do they begin to trust their code, though never entirely. A good developer always keeps a healthy level of skepticism. But the reason behind maintaining that skepticism will be addressed in another article. For now, let's examine whether the result of eight is, in fact, correct for Code 04.
To verify this, we need to understand how the compiler interprets what it is asked to compute. Since the compiler follows strict precedence rules, which are laid out in the operator precedence table, we can manually factor the calculation on line 13 and see if it is correct.
We start by identifying the operator with the highest precedence. In this case, it's the ++ operator applied to the variable v1. However, this operator is evaluated right to left. So in this case, its precedence will be changed and its effect will be applied to the variable v1 after all higher-precedence operations to its right have been completed. In our case, such an operator is the multiplication operator, which has priority over the left shift operator and the AND operator. So, the first operation is multiplying 1 by v2, which results in 5. Next, the left-shift operator is applied to shift the value 5 by one bit to the left. This generates a new value equal to 10. Then we perform the bitwise AND between v1 and 10. Since v1 is nine, when the AND operation is applied, the result is eight. Finally, after all operations on the right have been executed, the value of v1 is incremented by one. So when we factorize we get a value of eight, and when we complete the factorization we get a value of ten for v1.
You might now be thinking: "Ah, I get it. That's actually quite simple and clear." But is it really, dear reader? Are you sure you've truly understood how this process works? Let's find out. How about trying to solve the expression in the next code snippet below?
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char v1 = 9, 09. v2 = 5; 10. 11. PrintX(v1); 12. PrintX(v2); 13. PrintX(++v1 & 1 * v2 << 1); 14. PrintX(v1); 15. PrintX(v2); 16. } 17. //+------------------------------------------------------------------+
Code 05
Answer this without hesitation. If you can truly answer it correctly, then you really do understand how precedence works. But even so, just to mess with you a little, I'm going to show you a result, and I want you to tell me whether it's right or wrong.
When I ran this code, I got the result shown below:
Figure 07
Now tell me: why did my result come out as ten and not eight? There's a reason for this, which lies within the precedence rules. Well, dear reader, since I don't enjoy messing with people too much, let's dive into why these results differ. But for that, we need a new topic. I don't want to fry anyone's brain in just one article. We still have one more important topic to cover here.
BackTest and ForwardTest
The first thing you need to understand, my dear reader, is this: before writing any code, you should already know what result it will produce. Programming is not about writing code to get an unknown answer. It's exactly the opposite. You write code to produce a result you already know. And in this case, whether it's Code 04 or Code 05, I already knew what the result should be. Even knowing it might be incorrect due to the compiler warning, I still had a prediction. How could I know the result before writing the code? It seems pointless. Well, actually, many people tell you to code first and then see the result. But once you understand precedence, you can know the result before it's even printed.
To make this perfectly clear, let's do the following: as shown in the attached files, I'll be providing these code examples, so you can run them yourself and see exactly what I'm showing here. That way, you will truly understand why it is so important to know the answer ahead of time, even before writing the code that gives it to you.
Now for the real question. Which answer is correct: the one from Figure 06 or the one from Figure 07? And if I told you that both are correct? What would you say? Probably that I've lost my mind. After all, how can the same expression produce two different results and both be right? As insane as it may sound, both answers are correct. However, it's far more likely that the result you expected was the one shown in Figure 07. Meanwhile, the result from Figure 06 is a side effect caused by a misuse or misunderstanding of precedence. If we use parentheses in Code 04 to explicitly define operator order, the result will match the one seen in Figure 07. And it doesn't take much: just a small change to make the increment operator (++) execute with higher priority. In Code 05, because the increment operator is placed before the variable, it takes precedence over multiplication. But as explained earlier, in Code 04, the operator comes after the variable, meaning it only executes after the rest of the expression.
Don't believe me? Then how about we test it? Let's modify line 13 of Code 04 to the line shown below:
PrintX((v1++ & 1) * (v2 << 1));
And with this change, the compiler will no longer warn that the result might be unreliable. However, now that the same Code 04 has undergone this simple modification, it will give us the result seen in Figure 07. That's exactly what makes certain aspects of programming seem so complicated. Many people study programming for the wrong reasons. When in reality, the true purpose of programming is to get the computer to give us a known result faster. When we are trying to find an answer we don't already know, we can treat the computer's answer as a valid lead. But there's a limit to how much we can trust that. Within those limits, though, once your code has been tested, both with known values and with values you can manually verify, you can finally say: "MY PROGRAM CAN CALCULATE THIS OR THAT". Until then, your code probably has bugs because it hasn't been properly tested.
Alright, I will leave you to study this on your own time and reflect on what we've covered. But before we conclude this article, I want to touch on one last thing. Actually, one last operator which we haven't looked at yet. Let's move on to the last topic.
The Ternary Operator
This is my favorite operator. Anyone who follows my articles and here in the community is probably tired of seeing how often I use it. So why is it my favorite? Simple: it allows me to build logic without needing the IF statement. IF is a fundamental part of programming, as it controls the flow of execution. But there are many cases where we can use the ternary operator in situations where IF cannot be used. In my opinion, the ternary operator is an intermediate-level tool. You need to already have a solid understanding of other operators before you can really take full advantage of it. On top of that, you also need a good understanding of how flow control behaves within the ternary structure. It's rarely used in isolation. Most of the time, it is paired with an assignment operator or some logical operator. That's why I'm only going to explain how to read it for now. I'll try not to use it yet, at least, not just yet.
Let's take a look at a simple code example where the ternary operator could be applied. Sure, there are other ways this could be written. But the goal here is purely didactic. So don't worry about whether another method can be used. Just focus on the concept.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print("Factoring: { ", #X, " } is: ", X) 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. char v1 = 9, 09. v2 = 5; 10. 11. PrintX(v1); 12. PrintX(v2); 13. PrintX(v1 * ((v2 & 1) == true ? v2 - 1 : v2 + 1)); 14. PrintX(v2++); 15. PrintX(v1 * ((v2 & 1) == true ? v2 - 1 : v2 + 1)); 16. PrintX(v1); 17. PrintX(v2); 18. } 19. //+------------------------------------------------------------------+
Code 06
When Code 06 is executed, you will see the following result:
Figure 08
Since the ternary operator can be quite confusing for many people, especially beginners. I will explain carefully what's happening here. Still, you should take the time to study this entire article thoroughly in order to truly understand this. Read through it slowly and pay close attention to what I'm trying to explain. The topic is dense, and it might take some time to fully understand this.
Let's go back to code 06. Most of it is easy to understand. Of course, we use a macro, which I haven't yet explained how to integrate into your own programs. However, even this macro is relatively easy to understand. Since it was used in other examples throughout this article, the explanation here will apply to those as well. The macro defined on line 4 allows us to send information to the terminal, based on what is used in the code. But how exactly? To do this, we pass it an argument. When that argument is preceded by a hash symbol ( # ), the compiler is instructed to take the argument and display it exactly as it was written. Because of this, we can show the calculation being performed, along with the resulting value and, in some cases, the name of the variable used. It's a great way to debug certain types of code.
But that's only part of the story. What really matters here is how lines 13 and 15 work, as both lines make use of the ternary operator. To explain this in a simple way, let's focus on just one of these lines as our reference, since the other behaves in much the same way.
So, let's take line 13 and rewrite it in a way that doesn't use the ternary operator, but instead uses the IF statement. But why use an IF statement to explain the ternary operator? Because the ternary operator is essentially a condensed IF with the added benefit of acting as an expression, meaning it can be placed where a variable or value would normally go. However, despite this similarity, the ternary operator does not replace the IF statement. The ternary operator can't contain full code blocks within its structure, unlike IF, which allows code blocks but can't be used as a value-returning expression.
Alright. So, translating line 13 into an equivalent form using an IF statement, we would have something like this:
if ((v2 & 1) == true) value = v1 * (v2 - 1); else value = v1 * (v2 + 1); Print("Factoring: { v1 * ((v2 & 1) == true ? v2 - 1 : v2 + 1) } is: ", value);
The only detail to note here is the variable value, which technically doesn't exist. I'm using it solely to illustrate how things are interpreted by the compiler. So, consider value as a temporary variable that you won't have direct access to.
With that concept in mind, it becomes easier to understand how the compiler interprets a ternary operator. Notice that when using an IF statement, the code becomes much more readable. Of course, this wouldn't be practical in situations where the ternary operator is truly necessary. But for teaching purposes, it works. The same applies to the Print command seen in this small translation snippet. This command represents what the macro would be doing in the actual code.
At first glance, this kind of thing may seem quite complex. Especially because, as I've already mentioned, this type of coding represents what I consider to be an intermediate-level concept. So, don't rush yourself, dear reader. Take your time to study and practice gradually. But in any case, once you understand that the ternary operator is essentially a specialized IF, I believe everything will become much easier to absorb as we move forward into future articles.
Final Considerations
In this article, I've introduced and tried to explain what is, quite honestly, one of the most complex topics in programming, at least from a theoretical perspective. In practice, the subject of operators is far simpler and more intuitive to learn. That's because, once you see what each action or implementation produces as a result, it becomes much easier to understand that programmers aren't trying to create something unknown. Every program is designed to answer a question we already know the answer to. However, as the application evolves through testing and refinement, we eventually start using it to provide faster answers to specific, previously solved problems.
So to wrap up, I'll leave you with this advice: Study and make a point to practice with different types of challenges. Only through consistent practice will you be able to truly master how each operator should be used. Don't assume that simply having theoretical knowledge is enough, as it won't be. When it comes to working with operators, experience carries much more weight than theory. So get to practicing.
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15440





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use