Expressions and operators
Evaluate expressions by hand using precedence, and predict integer division, overflow and float rounding surprises.
What you will learn
- Evaluate arithmetic, comparison and logical expressions using precedence
- Predict the result of integer and float division
- Explain integer overflow and floating-point imprecision
- Use short-circuit evaluation correctly
On this page
Programs spend most of their time turning values into new values. An expression is a piece of code that produces one, and operators are the symbols that combine them. The rules are mostly familiar from school arithmetic, with a few differences that catch everyone once.
Expressions
An expression is anything that evaluates to a value: 7, price * 3, age >= 18. A statement performs an action, such as an assignment. Expressions nest, so (price * 3) + tax contains two smaller expressions inside it.
Operators take operands. Most are binary (3 + 4 has two operands); a few are unary (-x, !done).
Arithmetic operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ |
add | 7 + 2 |
9 |
- |
subtract | 7 - 2 |
5 |
* |
multiply | 7 * 2 |
14 |
/ |
divide | 7 / 2 |
depends on the language, see below |
% |
remainder (modulo) | 7 % 3 |
1 |
Power has no common symbol. Python and JavaScript write 2 ** 3 (8). Java has Math.pow(2, 3) and C has pow(2, 3) from <math.h>, and both return a float. In C and Java, ^ is not power: it is a bit-level operation.
The remainder operator is far more useful than it first looks. n % 2 == 0 tests for even numbers, and i % 7 wraps an index around a week.
Integer versus float division
Dividing 7 by 2 should give 3.5. Many languages give 3 instead, because both operands are integers and the answer is kept as an integer. Division and remainder of negative numbers is where languages disagree most, so each program below prints the same five questions.
Python
print(7 / 2)print(7 // 2)print(-7 // 2)print(7 % 3)print(-7 % 3)3.53-412/ always gives a float. // is floor division, which rounds toward negative infinity, so -7 // 2 is -4. The remainder takes the sign of the right operand.
JavaScript
console.log(7 / 2);console.log(Math.trunc(7 / 2));console.log(Math.trunc(-7 / 2));console.log(7 % 3);console.log(-7 % 3);3.53-31-1Every number is a float, so / never truncates by itself. Math.trunc cuts off the fraction. The remainder takes the sign of the left operand.
Java
public class Division { public static void main(String[] args) { System.out.println(7 / 2.0); System.out.println(7 / 2); System.out.println(-7 / 2); System.out.println(7 % 3); System.out.println(-7 % 3); }}3.53-31-1When both operands are int, the division is an integer division that truncates toward zero. One float operand (2.0) makes it a float division.
C
#include <stdio.h>
int main(void) { printf("%.1f\n", 7 / 2.0); printf("%d\n", 7 / 2); printf("%d\n", -7 / 2); printf("%d\n", 7 % 3); printf("%d\n", -7 % 3); return 0;}3.53-31-1C behaves like Java here: two int operands give an integer division that truncates toward zero. The %.1f in printf prints a float with one decimal place.
Side by side:
| Question | Python | JavaScript | Java | C |
|---|---|---|---|---|
7 divided by 2, whole result |
7 // 2 is 3 |
Math.trunc(7 / 2) is 3 |
7 / 2 is 3 |
7 / 2 is 3 |
-7 divided by 2, whole result |
-4 | -3 | -3 | -3 |
-7 % 3 |
2 | -1 | -1 | -1 |
If you will use the remainder of a negative number, check your languageās rule.
Comparison and logical operators
Comparisons produce booleans: == (equal), != (not equal), <, <=, >, >=. Mind that == compares and = assigns.
Logical operators combine booleans: and, or, not. Their spelling differs.
| Operation | Python | JavaScript, Java, C |
|---|---|---|
| and | and |
&& |
| or | or |
|| |
| not | not |
! |
The truth table is the same everywhere:
a |
b |
a and b |
a or b |
not a |
|---|---|---|---|---|
| false | false | false | false | true |
| false | true | false | true | true |
| true | false | false | true | false |
| true | true | true | true | false |
Short-circuit evaluation
and and or stop as soon as the answer is known. For false and X, the result is false whatever X is, so X is never evaluated. For true or X, likewise. This is useful for guards: check that a division is safe, then do it.
Python
d = 0print(d != 0 and 10 / d > 1)JavaScript
const d = 0;console.log(d !== 0 && 10 / d > 1);Java
public class ShortCircuit { public static void main(String[] args) { int d = 0; System.out.println(d != 0 && 10 / d > 1); }}C
#include <stdio.h>
int main(void) { int d = 0; printf("%d\n", d != 0 && 10 / d > 1); return 0;}Python, JavaScript and Java print False or false; C prints 0, because C writes false as the number 0. In every case the program does not crash, because d != 0 is false and the division on the right never runs. Swap the two conditions and the outcome depends on the language, as the third exercise shows.
Precedence
When an expression mixes operators, precedence decides which is applied first, and associativity decides the order among operators of equal rank. From highest to lowest, roughly:
| Level | Operators |
|---|---|
| 1 (first) | parentheses ( ) |
| 2 | unary minus, not |
| 3 | *, /, % (left to right) |
| 4 | +, - (left to right) |
| 5 | comparisons |
| 6 | and |
| 7 (last) | or |
Where power exists (Python, JavaScript), it sits between parentheses and unary minus and groups right to left. Here are some expressions evaluated step by step:
2 + 3 * 4 -> 2 + 12 -> 14(2 + 3) * 4 -> 5 * 4 -> 2010 - 4 - 3 -> 6 - 3 -> 3 (left to right)2 ** 3 ** 2 -> 2 ** 9 -> 512 (right to left; Python and JavaScript only)not true or true -> (not true) or true -> trueRather than memorise the table, use parentheses whenever the order is not obvious. Parentheses cost nothing and tell the next reader what you meant. This program checks the middle three lines and the last:
Python
print(2 + 3 * 4)print((2 + 3) * 4)print(10 - 4 - 3)print(not True or True)JavaScript
console.log(2 + 3 * 4);console.log((2 + 3) * 4);console.log(10 - 4 - 3);console.log(!true || true);Java
public class Precedence { public static void main(String[] args) { System.out.println(2 + 3 * 4); System.out.println((2 + 3) * 4); System.out.println(10 - 4 - 3); System.out.println(!true || true); }}C
#include <stdbool.h>#include <stdio.h>
int main(void) { printf("%d\n", 2 + 3 * 4); printf("%d\n", (2 + 3) * 4); printf("%d\n", 10 - 4 - 3); printf("%d\n", !true || true); return 0;}The first three lines are 14, 20 and 3 in all four languages. The last is True in Python, true in JavaScript and Java, and 1 in C.
Two numeric traps
Overflow
Computers store integers in a fixed number of bits. A 32-bit signed integer holds values up to 2,147,483,647. Add one more and the result does not fit. What happens next depends on the language, so each tab below triggers a different kind of limit.
Python
print(2 ** 64)18446744073709551616Python integers grow as needed, using more memory instead of overflowing.
JavaScript
console.log(2 ** 53 + 1);9007199254740992Numbers are floats, so whole numbers stop being exact above 253. The answer is off by one, silently.
Java
public class Overflow { public static void main(String[] args) { System.out.println(Integer.MAX_VALUE + 1); }}-2147483648The 32-bit int wraps around to the most negative value.
C
#include <limits.h>#include <stdio.h>
int main(void) { unsigned int u = UINT_MAX; u = u + 1; printf("%u\n", u); return 0;}0Unsigned values wrap to zero. Signed overflow in C is undefined behaviour, which means anything may happen, so this example uses an unsigned value.
Some languages wrap around, some lose precision, some treat overflow as a bug, and some, like Python, simply use more memory. Know which you are in when you handle large counts, sizes or money.
Floating-point imprecision
Floats store numbers in binary. Just as one third cannot be written exactly in decimal, 0.1 cannot be written exactly in binary. The error is tiny, but real.
Python
print(0.1 + 0.2)print(0.1 + 0.2 == 0.3)print(abs((0.1 + 0.2) - 0.3) < 1e-9)JavaScript
console.log(0.1 + 0.2);console.log(0.1 + 0.2 === 0.3);console.log(Math.abs((0.1 + 0.2) - 0.3) < 1e-9);Java
public class Floats { public static void main(String[] args) { System.out.println(0.1 + 0.2); System.out.println(0.1 + 0.2 == 0.3); System.out.println(Math.abs((0.1 + 0.2) - 0.3) < 1e-9); }}C
#include <math.h>#include <stdio.h>
int main(void) { printf("%.17g\n", 0.1 + 0.2); printf("%d\n", 0.1 + 0.2 == 0.3); printf("%d\n", fabs((0.1 + 0.2) - 0.3) < 1e-9); return 0;}All four languages agree on the numbers: 0.30000000000000004, then the comparison is false (0 in C), then the tolerance check is true (1 in C). Every language that uses standard floats gives the same answer; this is not a Python quirk. Two consequences:
- Never compare floats with
==. Check that the difference is smaller than a tolerance, as in the last line. - Never store money in floats. Use integers of the smallest unit (cents) or a decimal type, which your language course covers.
Common pitfalls
=instead of==. One assigns; the other compares.- Integer division surprise.
1 / 2is 0 in Java and C. Make one operand a float if you want 0.5. - Trusting remainder of negatives. It differs between languages.
- Comparing floats for equality. Use a tolerance.
- Relying on memorised precedence. Add parentheses when in doubt.
- Overflow in loops and counters. Check the range before the arithmetic, not after.
Evaluate each, writing intermediate steps. In (c) and (e) the numbers are whole and / means integer division, as in Java and C, so 7 / 2 is 3: (a) 10 - 4 - 3, (b) 2 + 3 * 4 - 1, (c) 17 % 5 + 17 / 5, (d) not (3 > 2) or 5 == 5, (e) 7 / 2 * 2 + 7 % 2. Expected: a number for (a), (b), (c), (e) and true or false for (d).
Show solution
(a) Left to right: 6 - 3 = 3.
(b) Multiplication first: 2 + 12 - 1 = 13.
(c) 17 % 5 is 2 and 17 / 5 is 3, so 5.
(d) 3 > 2 is true, not true is false, 5 == 5 is true, so false or true = true.
(e) 7 / 2 is 3, 3 * 2 is 6, 7 % 2 is 1, so 7. (This is the identity (a / b) * b + a % b == a for whole numbers.)
Check these in a REPL afterwards.
Explain in two sentences why 0.1 + 0.2 == 0.3 is false, and write a safer comparison. Expected: the comparison prints true for 0.1 + 0.2 against 0.3, and false for 0.1 + 0.2 against 0.31.
Show solution
0.1 and 0.2 cannot be represented exactly in binary floating point, so their sum is the closest representable value, 0.30000000000000004, which differs slightly from the closest representation of 0.3. A safer comparison asks whether they are close enough.
Python
def close_enough(a, b): return abs(a - b) < 1e-9
print(close_enough(0.1 + 0.2, 0.3))print(close_enough(0.1 + 0.2, 0.31))JavaScript
function closeEnough(a, b) { return Math.abs(a - b) < 1e-9;}
console.log(closeEnough(0.1 + 0.2, 0.3));console.log(closeEnough(0.1 + 0.2, 0.31));Java
public class CloseEnough { static boolean closeEnough(double a, double b) { return Math.abs(a - b) < 1e-9; }
public static void main(String[] args) { System.out.println(closeEnough(0.1 + 0.2, 0.3)); System.out.println(closeEnough(0.1 + 0.2, 0.31)); }}C
#include <math.h>#include <stdio.h>
static int close_enough(double a, double b) { return fabs(a - b) < 1e-9;}
int main(void) { printf("%d\n", close_enough(0.1 + 0.2, 0.3)); printf("%d\n", close_enough(0.1 + 0.2, 0.31)); return 0;}The output is true then false (1 then 0 in C). The functions here are only a named wrapper around the comparison; the lesson on functions explains the syntax.
A program has d = 0. Which of these crashes, and why? (a) d != 0 and 10 / d > 1 (b) 10 / d > 1 and d != 0. Answer for Python, then say what you expect in JavaScript. Expected: one of the two crashes in Python, and JavaScript behaves differently from Python.
Show solution
(b) crashes in Python. It evaluates 10 / d first, which divides by zero. In (a) the d != 0 test is false, so and stops and never reaches the division. Order the cheap safety check first.
JavaScript differs: 10 / 0 is Infinity rather than an error, so (b) runs to the end and gives false. Java throws ArithmeticException for integer division by zero, and C typically crashes the program.
| Language | (a) d != 0 and 10 / d > 1 |
(b) 10 / d > 1 and d != 0 |
|---|---|---|
| Python | False | ZeroDivisionError |
| JavaScript | false | false (no error) |
| Java | false | ArithmeticException |
| C | 0 | crashes (division by zero) |
Summary
- Expressions produce values; precedence and associativity decide evaluation order, and parentheses make intent clear.
- Integer division, the sign of remainders, integer overflow and float rounding all vary by language or type, so check them.
andandorshort-circuit, and floats should be compared with a tolerance, not==.
Next: Text, characters and encodings.