JavaRush /Courses /Go SELF /Arithmetic and operator precedence

Arithmetic and operator precedence

Go SELF
Level 7, Lesson 3
Available

1. Precedence and evaluation order

Operator precedence is about the order in which Go evaluates an expression when you wrote several operations in a row. At first it feels like, “Well, it’s obvious.” And then suddenly a discount is computed “wrong,” percentages turn into magic, and the final amount “drifts” — and it’s not the computer’s fault, it’s the expression without parentheses.

In programming (and in life), the most unpleasant mistakes are the ones that don’t crash with a panic but instead give a wrong, yet “plausible-looking” result. These are exactly the kinds of bugs that often come from precedence and integer division. So now we’ll do two things: understand the evaluation order and learn to predict the result.

Arithmetic operators

You’ve already encountered arithmetic: + - * / %. Now it’s useful to “wipe the glass a bit” and notice an important detail: the same symbol can behave predictably, but the context decides what exactly is happening. For example, -5 is not “subtraction,” it’s unary minus (an operator with one operand). And 10 - 5 is subtraction.

And even more importantly: in Go, arithmetic is almost always strictly typed. That is, expressions on int produce results of type int. This is especially noticeable with division.

A small “just in case” diagnostic:

package main

import "fmt"

func main() {
	a := 10
	b := 3
	fmt.Printf("a=%d, b=%d\n", a, b) // a=10, b=3
}

Operator precedence

When we write an expression like 2 + 3*4, Go doesn’t read it left to right “like a tired person.” It reads it by the rules: multiplication first, then addition.

It’s convenient to keep a very short “school” version in mind, which is fully sufficient for us right now: parentheses are strongest, then * / %, then + -. This alone is enough to avoid 90% of common traps.

Mini precedence table (what you need right now):

Group Operators How to remember
1 (strongest)
( ... )
“Parentheses are the boss”
2
* / %
“Multiply-divide-remainder are one team”
3 (weaker)
+ -
“Addition/subtraction come later”

Important: the % operator (remainder) lives at the same level as * and /. That means a + b%10 is a + (b%10), not (a+b)%10.

Left associativity

When operators have the same precedence (for example, * and /), a second question appears: “OK, but which one goes first?” Answer: in typical cases, Go evaluates left to right.

This is called (don’t worry) associativity. For us, it’s enough to remember: 8 / 2 * 2 is computed as ((8 / 2) * 2), not as 8 / (2 * 2).

Let’s look at concrete numbers:

package main

import "fmt"

func main() {
	fmt.Println(8/2*2)   // 8
	fmt.Println(8/(2*2)) // 2
}

And this is not a “Go quirk”; it’s normal expression math in most languages. That’s why, if you want a different order explicitly — use parentheses, even if it’s “obvious.”

Parentheses as a way to make code more obvious

In reality, precedence is not something you should keep in your head like a multiplication table. In reality, precedence is something you should be able to check, and you should write code so the precedence is obvious.

Parentheses are not a sign of weakness (even if coworkers sometimes pretend they are). Parentheses are like labels on wires: “red is plus, don’t touch.”

Let’s compare two versions:

package main

import "fmt"

func main() {
	price := 200
	qty := 3

	total1 := price*qty + 50
	total2 := (price * qty) + 50

	fmt.Println(total1, total2) // 650 650
}

Both work the same, but total2 is faster to read. Your future you will thank your current you. (A rare moment in life when this actually happens.)

2. Integer division and remainder

Integer division

Now — about division. And here comes that moment where many people say: “Wait, what?!”

If both operands are integers (int, int64, etc.), then / performs integer division, meaning the fractional part is simply discarded.

It does not round “by math rules,” it does not automatically turn into float64, it does not try to guess what you “meant.” It just takes the whole part.

package main

import "fmt"

func main() {
	fmt.Println(7 / 2) // 3
	fmt.Println(9 / 3) // 3
	fmt.Println(1 / 2) // 0
}

1/2 == 0 is normal for integer arithmetic. That’s exactly why it’s very convenient for counters, indexes, “how many full boxes,” and money amounts in kopecks/cents.

Division of negative numbers

A separate nuance: what happens with negative numbers. In Go, integer division goes toward zero. This means -7/2 will be -3, not -4.

package main

import "fmt"

func main() {
	fmt.Println(-7 / 2) // -3
	fmt.Println(-1 / 2) // 0
}

Why is this important? Because the % remainder is tied to division. And if you do logic with negatives (for example, balance, debt, difference), it’s better to know in advance that the language behaves this way, not “the way you wish it did on a Friday evening.”

Remainder

The % operator is the remainder of division. It works only with integer types and is great for “splitting a number” into parts: seconds and minutes, rubles and kopecks, pages and items.

A very useful formula worth remembering as a fact of life:

If a and b are integers, then: a == (a/b)*b + (a%b)

Let’s verify with an example:

package main

import "fmt"

func main() {
	a := 17
	b := 5
	fmt.Println(a/b, a%b) // 3 2
	fmt.Println((a/b)*b + (a%b)) // 17
}

Practice: euros and cents

The most “real-life” example for integer division is money. With float64, money often produces “tails” like 10.0000000002, but with integer kopecks/cents everything is strict.

Suppose we have an amount in kopecks (or cents), and we want to print “euros and cents.”

package main

import "fmt"

func main() {
	totalCents := 12345

	euros := totalCents / 100
	cents := totalCents % 100

	fmt.Printf("%d euros %02d cents\n", euros, cents) // 123 euros 45 cents
}

Notice %02d: it’s just an output format so cents are always printed with two digits (for example, 05 instead of 5). We’re not diving into formatting as a separate topic right now, but this small trick makes the output “human-friendly.”

Percentages and operation order

Percentages are a classic source of bugs. The reason is simple: percentages almost always look like a “simple formula,” but with integers you suddenly run into division and operation order.

Imagine: the price price = 199 euros, the discount is 10%. How do we compute the discount amount in euros?

A naive attempt often looks like this: discount := price * 10 / 100. This is already pretty good: multiplication happens before division, all good.

But the mistake appears when someone writes: discount := price * (10 / 100). Here (10/100) is integer division and yields 0. So the discount becomes price*0, i.e. 0.

Let’s show them side by side:

package main

import "fmt"

func main() {
	price := 199

	d1 := price * 10 / 100
	d2 := price * (10 / 100)

	fmt.Println(d1) // 19
	fmt.Println(d2) // 0
}

This is a great example of why parentheses are a powerful tool, but you can also “make it worse” with them if you don’t understand division.

3. Practice: an order, a discount, and splitting the total

Order calculation scheme

Let’s continue our mini app. Let’s call it условно “Coffee Shop”: we calculate the order cost, the discount, and the final total. We’ll store everything in kopecks (or cents) as int64 so we don’t argue with floating point.

Below is the computation scheme so you don’t drown in a single 200-character line.

flowchart TD
    A[Unit price, kopecks] --> C[Compute subtotal]
    B[Quantity] --> C
    C --> D[Discount, kopecks]
    D --> E[Total, kopecks]
    E --> F[Rubles and kopecks]

The scheme is simple: first subtotal, then discount, then total, then “split” into rubles/kopecks for printing.

Code: subtotal, discount, and total

Let’s make a small but coherent fragment: we input the price (in kopecks), the quantity, and the discount percent. We compute carefully, without mixing everything into one line.

package main

import (
	"fmt"
)

func main() {
	var priceK int64
	var qty int64
	var discountPercent int64

	fmt.Scan(&priceK, &qty, &discountPercent)

	subtotal := priceK * qty
	discount := subtotal * discountPercent / 100
	total := subtotal - discount

	fmt.Println(total)
}

The most important part here is the line discount := subtotal * discountPercent / 100. Because of precedence, it’s computed as (subtotal * discountPercent) / 100, i.e. “multiply first, then divide.” This is usually what you want for percentages in integer arithmetic.

Why it’s better to multiply before dividing

This is not only about “precedence.” It’s also about the fact that integer division discards the fractional part, which means you lose precision.

If you divide too early, you can lose information even before multiplying. The total loss can be surprising.

Let’s compare two discount formulas in kopecks:

1) subtotal * percent / 100
2) subtotal / 100 * percent

They are not equal because of truncation.

package main

import "fmt"

func main() {
	subtotal := int64(199) // cents for simplicity
	percent := int64(10)

	a := subtotal * percent / 100
	b := subtotal / 100 * percent

	fmt.Println(a) // 19
	fmt.Println(b) // 10
}

In the second variant, subtotal/100 first turned into 1, because 199/100 == 1. And only then it was multiplied by 10.

Where the “extra cents” go with a discount

If you compute a discount in kopecks/cents, you almost always face the fact that “by math” it comes out fractional, but there is no fraction of a cent in integer cents. For example, a 15% discount on 99 cents is 14.85 cents. What do you do with the 0.85?

In this lecture we don’t introduce separate rounding functions (that’s a separate topic), but it’s important to understand the basic effect: in integers, the fraction is simply lost. That means you always round toward zero on division.

Let’s see:

package main

import "fmt"

func main() {
	subtotal := int64(99)
	percent := int64(15)

	discount := subtotal * percent / 100
	fmt.Println(discount) // 14
}

This is not a “Go error.” It’s just the chosen data type: integers.

Splitting a total among people and computing the remainder

A very typical situation: you need to split a bill evenly among N people. In integer cents you’ll get a “remainder” that someone has to cover (or you decide the coffee shop gifts those cents to the world).

package main

import "fmt"

func main() {
	totalC := 1000  // 10 euros 00 cents
	people := 3

	perPerson := totalC / people
	remainder := totalC % people

	fmt.Println(perPerson, remainder) // 333 1
}

Here we got: everyone pays 333 cents, and 1 cent remains as the “tail.”

Why one line doesn’t make code smarter

Once you’ve learned precedence, there’s a temptation to write like this:

total := priceC*qty - priceC*qty*discountPercent/100

The compiler will accept it. But a human (including you a week later) will slowly become sad.

It’s better to be a bit longer but clear. We already did it this way, and it’s genuinely good style for a beginner:

package main

import "fmt"

func main() {
	priceC := int64(15000)
	qty := int64(2)
	discountPercent := int64(10)

	subtotal := priceC * qty
	discount := subtotal * discountPercent / 100
	total := subtotal - discount

	fmt.Println(total) // 27000
}

Numeric literals and dividing 7/2

Sometimes the confusion is amplified by the fact that literals like 7 and 2 look like “just numbers.” In Go, numeric constants first exist as “ideal numbers (untyped),” but when you use them in a regular expression, they get a default type. For integer constants it’s int, for floating-point it’s float64.

That’s why 7/2 is int divided by int, i.e. integer division. But 7.0/2.0 is already floating-point.

Let’s show the difference without type conversions (just by using different literals):

package main

import "fmt"

func main() {
	fmt.Println(7 / 2)     // 3
	fmt.Println(7.0 / 2.0) // 3.5
}

4. Common mistakes

Mistake #1: expecting a “fraction” from int / int.
When you divide int by int, you get int, and the fractional part disappears. The most painful case is when you divide small numbers and get 0, and then multiply by something serious and wonder “why is everything zero.” This isn’t fixed by “magic,” it’s fixed by understanding which arithmetic is being performed: integer or floating-point.

Mistake #2: adding parentheses that change the meaning of percentages.
The expression subtotal * percent / 100 is usually correct. But subtotal * (percent / 100) is almost always wrong with integers, because (percent/100) often becomes 0. Parentheses aren’t just decoration; they change evaluation order, so you must use them deliberately.

Mistake #3: forgetting that % has high precedence.
Beginners sometimes read a + b%10 as “add first, then take the remainder.” But in reality the remainder b%10 is computed first, and only then it’s added to a. If you want something else, you need parentheses: (a + b) % 10.

Mistake #4: trying to “round” via int(x/100*percent) in integer arithmetic.
If x is an integer, then x/100 has already lost its fractional part, and no later multiplication will bring it back. It’s often more correct to multiply first and divide later, not the other way around. This is not an “always do this” rule, but it’s a good default for percentages and proportions in integer arithmetic.

Mistake #5: turning the calculation into one line and losing control of the meaning.
Technically, Go allows long expressions, and you really can “keep precedence in your head.” But in real code, it’s more important that the expression is readable: intermediate variables (subtotal, discount, total) are not “extra code,” they make the formula visually checkable and therefore more reliable.

1
Task
Go SELF, level 7, lesson 3
Locked
Warehouse packing
Warehouse packing
1
Task
Go SELF, level 7, lesson 3
Locked
Two estimates
Two estimates
1
Task
Go SELF, level 7, lesson 3
Locked
Discount Without Fractions
Discount Without Fractions
1
Task
Go SELF, level 7, lesson 3
Locked
Per-minute call billing
Per-minute call billing
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION