Module 4 · Operators and Type Conversion
Relational and Logical Operators, and Short Circuit
In this lesson
- Compare two values with
<,<=,>,>=,==and!=. - Join two answers with
&&and||, and predict which side never runs. - Pick one of two values with the ternary operator, in a single expression.
Zara wants to know whether a mark sits between 1 and 10. She writes 1 < mark < 10, the way the range is written on paper.
It compiles. The Playground says nothing at all. And it answers 1 for a mark of 42, for a mark of 900, and for every other number she tries.
The line is not broken. It is a perfectly ordinary expression that means something other than what she read.
A comparison is an int, worth 1 or 0
Module 2 lesson 4 said this once. This lesson depends on it, so here it is again with the six operators.
The six relational operators
a < b is a less than b
a <= b is a less than or equal to b
a > b is a greater than b
a >= b is a greater than or equal to b
a == b is a equal to b two equals signs, always
a != b is a not equal to b
- Every one of them produces an
int, and thatintis 1 or 0. Never anything else. =is assignment.==is a question. They are different operators that look alike.- The answer is a value, so you can store it, print it and do arithmetic on it.
#include <stdio.h>
int main(void)
{
int mark = 42;
printf("mark < 50 %d\n", mark < 50);
printf("mark >= 40 %d\n", mark >= 40);
printf("mark == 42 %d\n", mark == 42);
printf("mark != 42 %d\n", mark != 42);
return 0;
}
mark < 50 1
mark >= 40 1
mark == 42 1
mark != 42 0
Nothing here decides anything. Four questions were asked and four numbers came back.
Why 1 < mark < 10 compiles and lies
Relational operators group from the left. So C reads Zara's line as (1 < mark) < 10.
The bracketed part is a comparison, so its value is 1 or 0. Then C asks whether that 1 or 0 is less than 10. It always is.
#include <stdio.h>
int main(void)
{
int mark = 900;
printf("1 < mark %d\n", 1 < mark);
printf("that, < 10 %d\n", (1 < mark) < 10);
printf("written chained %d\n", 1 < mark < 10);
return 0;
}
1 < mark 1
that, < 10 1
written chained 1
The last two lines are the same expression written twice. The brackets only make the reading visible.
On the Playground's command line this is completely silent, because the check lives behind -Wall. A local gcc -Wall gives two warnings: comparisons like 'X<=Y<=Z' do not have their mathematical meaning and comparison of constant '10' with boolean expression is always true.
So a range needs two separate comparisons, joined by the operator in the next section.
&& and ||, and the truth table a program can print
&& is "and": 1 when both sides are true. || is "or": 1 when at least one side is true.
"True" here means "not zero", the Module 2 rule. The result is again a plain int worth 1 or 0.
#include <stdio.h>
int main(void)
{
printf("p q p&&q p||q !p\n");
printf("0 0 %d %d %d\n", 0 && 0, 0 || 0, !0);
printf("0 1 %d %d %d\n", 0 && 1, 0 || 1, !0);
printf("1 0 %d %d %d\n", 1 && 0, 1 || 0, !1);
printf("1 1 %d %d %d\n", 1 && 1, 1 || 1, !1);
return 0;
}
p q p&&q p||q !p
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0
Zara's range check now writes itself: mark > 1 && mark < 10. Two questions, one joining word.
So the truth table is not something to memorise. It is four lines a program will print for you in ten seconds.
Short circuit: the right side may never run
C promises more than the table. It promises an order, and a chance to stop early.
For a && b, C evaluates a first. If a is 0 the answer is already 0, so b is never evaluated at all.
For a || b, C evaluates a first. If a is not zero the answer is already 1, so b is skipped.
You can watch it happen. printf returns the number of characters it printed, so a printf works as the right operand.
#include <stdio.h>
int main(void)
{
int stock = 0;
printf("start\n");
int a = stock && printf(" the right side of && ran\n");
int b = 1 || printf(" the right side of || ran\n");
printf("a %d, b %d\n", a, b);
return 0;
}
start
a 0, b 1
Neither message appeared. stock was 0, so the && stopped; the 1 was already true, so the || stopped.
So short circuit is how you guard a dangerous operation. Put the check on the left of &&, and the dangerous part runs only when the check passed.
! turns a number into an answer, and !! flattens one
! is "not". It gives 0 for any nonzero value, and 1 for zero.
Apply it twice and you get a value that is 1 or 0 and nothing else. !!7 is 1, !!0 is 0.
#include <stdio.h>
int main(void)
{
int credits = 7;
printf("credits %d\n", credits);
printf("!credits %d\n", !credits);
printf("!!credits %d\n", !!credits);
return 0;
}
credits 7
!credits 0
!!credits 1
The !! pair is a known idiom for "reduce this to exactly 1 or 0". It is not a special operator; it is ! used twice.
So "has credits" and "how many credits" become two different values, and only one of them is safe to compare against 1.
The ternary: one expression, two possible values
Every operator so far took two operands. This one takes three, and it is the only such operator in C.
The conditional operator
condition ? value_if_true : value_if_false
conditionis evaluated first. Zero counts as false, anything else as true.- Exactly one of the two values is evaluated, the same short-circuit promise.
- The whole thing is a value, so it can sit on the right of an
=or inside aprintf.
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int larger = (a > b) ? a : b;
printf("%d\n", larger);
return 0;
}
91
That output is for the input 91 47. Module 5 will write the same thing with if over four lines; here it is one.
The track's advice is one line long. Use the ternary when both results are values of the same kind. Reach for if the moment a branch does work instead of producing a value.
A comparison goes into a box, and the box is printed with %d.
#include <stdio.h>
int main(void)
{
int age = 0;
scanf("%d", &age);
int adult = age >= 18;
printf("%d\n", adult);
return 0;
}
1
That output is for the input 18. The boundary is in the operator: >= includes 18, > would not.
Two comparisons, joined with &&, printed as one number.
#include <stdio.h>
int main(void)
{
int mark = 0;
scanf("%d", &mark);
int in_range = mark >= 1 && mark <= 10;
printf("%d\n", in_range);
return 0;
}
0
That output is for the input 42. Try 1, 10 and 11: the two ends are the tests worth running, and the operator you chose decides them.
Short circuit used on purpose. The divisor is checked on the left, so the division on the right is unreachable when it would be undefined.
#include <stdio.h>
int main(void)
{
int n = 0;
int d = 0;
scanf("%d %d", &n, &d);
int big = (d != 0) && (n / d > 10);
printf("%d\n", big);
return 0;
}
0
That output is for the input 500 0. The division never happened, so there was nothing to go wrong. Swap the two sides and the program becomes undefined for that same input.
Where this is used
- Every form you have filled in. A signup form checks a length and a character range with the same
&&chain, on the server, before the value reaches a database. - Leap years. The rule is three comparisons joined by
&&and||. The same expression sits in the Linux date utility, in a spreadsheet and in Exercise 2 below. - Feature flags. A release check reads
flag_on && user_is_beta. Short circuit means the second, slower lookup happens only for the small group where the flag is on at all. - Guarding a pointer. Real C is full of
p != NULL && p->ready. Module 11 explains the arrow; the shape is exactly Example 3, and the crash it prevents is worse.
Common mistakes
1. Writing a range the way mathematics writes it.
int ok = 1 < mark < 10;
printf("%d\n", ok);
Silent on the Playground, because both checks for it live behind -Wall; a local gcc -Wall reports comparisons like 'X<=Y<=Z' do not have their mathematical meaning. It prints 1 for every value. Write mark > 1 && mark < 10.
2. One equals sign where two were meant.
int matched = code = 5;
printf("matched %d\n", matched);
No message at either command line. The inner = is an assignment whose value is 5, so matched is always 5 and code has been overwritten. Two equals signs ask; one equals sign tells.
3. Using & where && was needed.
int big = (d != 0) & (n / d > 10);
No message at either command line, and for most inputs the answer is even correct. But & has no short circuit, so both sides are evaluated and the division runs with d at zero, which is undefined behaviour. One character decides whether the program is safe.
4. Comparing two doubles with ==.
printf("%d\n", 0.1 + 0.2 == 0.3);
No message at either command line, and it prints 0. Module 2 lesson 3 showed why those two values differ in their last bits. Compare a difference against a small tolerance instead.
Amara is validating a mark that must land between two given bounds, both included.
Input. One line with three integers low, high and x.
Output. One line with 1 if x is between low and high, both included, otherwise 0.
Constraints. -1000 <= low <= high <= 1000, and -2000 <= x <= 2000.
Sample. Input 1 10 10 gives 1. Input 1 10 11 gives 0.
#include <stdio.h>
int main(void)
{
int low = 0;
int high = 0;
int x = 0;
scanf("%d %d %d", &low, &high, &x);
/* Two comparisons and one &&. Both ends are included. */
return 0;
}
Not graded in this module. Run it on both bounds before you believe it.
Run in CompilerDavid is writing the calendar check every project eventually needs.
A year is a leap year when it divides by 4 and not by 100, or when it divides by 400.
Input. One line with one integer y.
Output. One line with 1 if y is a leap year, otherwise 0.
Constraints. 1 <= y <= 999999.
Sample. Input 2024 gives 1. Input 1900 gives 0. Input 2000 gives 1.
#include <stdio.h>
int main(void)
{
int y = 0;
scanf("%d", &y);
/* Three remainders, two comparisons joined by &&, then one ||. */
return 0;
}
Graded as leap-year. The whole rule fits in one expression; 1900 and 2000 are the two years that tell you whether you got it right.
Kenji wants the larger of two scores, without writing a single if.
Input. One line with two integers.
Output. One line with the larger of the two. If they are equal, print that value.
Constraints. Each value is between -1000000 and 1000000.
Sample. Input 91 47 gives 91.
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
/* One ternary. Equal values make both branches correct. */
return 0;
}
Not graded in this module. Then write the smaller one too, by changing exactly one character.
Run in CompilerZara wants to see a whole truth table for one expression, printed by the program itself.
Input. One line with two integers p and q, each 0 or 1.
Output. Four lines: the values of p && q, p || q, !p and p != q, one per line, in that order.
Constraints. Each of p and q is 0 or 1.
Sample. Input 1 0 gives the four lines 0, 1, 0, 1.
#include <stdio.h>
int main(void)
{
int p = 0;
int q = 0;
scanf("%d %d", &p, &q);
/* Four printf lines, each one expression. No loop, no condition. */
return 0;
}
Not graded in this module. Run all four input pairs and you have written the table of this lesson's third section.
Run in CompilerCommon doubts
Is the answer always exactly 1, or just any nonzero value?
Exactly 1. C guarantees that
<,==,&&,||and!all produce 1 or 0. Other people's functions may return any nonzero value for true, which is why!!exists.Why is there both
&and&&?They do different jobs.
&&joins two answers and stops early;&works on the bits of two numbers and always evaluates both. Lesson 4 is entirely about the second one.Can I compare a
charwith a number?Yes, because a
charis a number.ch >= '0' && ch <= '9'is the standard digit test, and it is comparing 48 and 57.Does the ternary work with different types on the two sides?
It compiles, and the two values are converted to one common type first. Keep both sides the same type until lesson 6 explains the conversion rules.
Is short circuit just an optimisation?
No. It is written into the standard, so every C compiler must do it. That is what makes it safe to put a guard on the left of
&&.
Key takeaways
- Every comparison produces an
intthat is exactly 1 or 0. 1 < x < 10compiles, is always 1, and is silent on the Playground.&&and||join answers; zero is false and everything else is true.- Short circuit is a rule of the language: the right operand may never be evaluated.
!flips an answer and!!reduces any value to 1 or 0.- The ternary is an expression with a value, best used when both sides are values.
Next you stop writing total = total + x and meet the shorter forms, one of which hides undefined behaviour.
End of lesson 2
Mark it done, and your progress moves with you.
Next: Assignment Shortcuts, ++ and --