13. Numbers and math

Go numbers come in two main kinds, do the usual arithmetic, and plug into standard library packages for more helpers. This chapter covers what you need for game logic: rounding, random numbers, basic maths, and moving between the two kinds.

Integers and floats

Go has two common number types:

  • int. A whole number with no decimal part: 0, 7, -42.
  • float64. A number with a decimal point: 3.14, 0.5, -1.0.

In Go you do have to care which is which. 7 is an int, 7.0 is a float64, and the two kinds do not mix in variables, as you will see below.

%T tells you which you have:

fmt.Printf("%T\n", 7)    // int
fmt.Printf("%T\n", 7.0)  // float64
fmt.Printf("%T\n", 3.14) // float64

Arithmetic

The familiar operators are all here:

Operator Name Example Result
+ addition 3 + 2 5
- subtraction 7 - 4 3
* multiplication 6 * 7 42
/ division, whole numbers 7 / 2 3
/ division, decimals 7.0 / 2 3.5
% modulo (remainder) 7 % 3 1

Two are worth a closer look:

Division / depends on the type. Dividing two whole numbers gives a whole number, and everything after the decimal point is dropped: 7 / 2 is 3. It drops toward zero, so -7 / 2 is -3. With a decimal in the calculation, you get a decimal: 7.0 / 2 is 3.5.

Modulo % is the remainder after whole number division. 7 % 3 is 1 because 7 = 2 * 3 + 1. It is the standard way to ask "is this number even?" — n % 2 is 0 for evens and 1 for positive odds. It only works on whole numbers.

fmt.Println(7 / 2)   // 3
fmt.Println(7.0 / 2) // 3.5
fmt.Println(7 % 2)   // 1

Go has no power operator. ^ exists, but it does something else entirely: 2 ^ 8 compiles, runs, and gives 10. To raise a number to a power, use math.Pow from the next section.

Open exercises/13/01-arithmetic.go. Predict each printed result before running, then run and check.

The math package

Go's math package has the helpers you reach for most. Import it next to fmt:

import (
    "fmt"
    "math"
)
Function What it does
math.Floor(x) Largest whole value not greater than x. Floor(3.7) is 3.
math.Ceil(x) Smallest whole value not less than x. Ceil(3.2) is 4.
math.Abs(x) The absolute value. Abs(-5) is 5.
math.Sqrt(x) Square root.
math.Pow(x, y) x to the power y. Pow(2, 8) is 256.
min(a, b) The smaller of two numbers. Built-in, no import.
max(a, b) The larger of two numbers. Built-in, no import.
math.Pi The constant 3.1415... (a value, not a function).

Call them like any other function:

fmt.Println(math.Floor(3.7)) // 3
fmt.Println(math.Ceil(3.2))  // 4
fmt.Println(math.Sqrt(16))   // 4
fmt.Println(min(7, 3))       // 3
fmt.Println(math.Pi)         // 3.141592653589793

These calls, and the rest of the table, are in exercises/13/02-math-package.go.

The math functions take and return float64, even when the result prints as 3. fmt.Println leaves off the decimal point when a float64 has nothing after it. min and max are built into Go and always available; they give back the same type you gave them.

Showing decimals with %.2f

fmt.Println prints a float64 however it sees fit. For a fixed number of decimals, use the marker %.2f in fmt.Printf. The 2 is the number of decimals to show, and the value is rounded to fit:

price := 3.14159
fmt.Printf("Price: %.2f\n", price) // Price: 3.14

Use it for anything a person reads. fmt.Println(1000000.0) prints 1e+06, and 0.1 + 0.2, when held in variables, prints 0.30000000000000004, because computers store decimals only approximately. %.2f shows these as 1000000.00 and 0.30.

Random numbers

Random numbers live in the math/rand package. Import it next to fmt, and call it by the last part of its name, rand:

import (
    "fmt"
    "math/rand"
)
  • rand.Float64() gives a float64 from 0.0 up to, but not including, 1.0.
  • rand.Intn(n) gives a whole number from 0 to n-1. To get a six-sided die, add one: rand.Intn(6) + 1.
fmt.Println(rand.Float64())     // for example 0.6238...
fmt.Println(rand.Intn(6) + 1)   // for example 4
fmt.Println(rand.Intn(100) + 1) // for example 73

These lines are in exercises/13/03-random.go.

rand.Intn(6) never gives 6. Its results are 0, 1, 2, 3, 4 and 5, so the + 1 is what makes a die. Forgetting it is a common bug, and an easy one to miss, since the results still look random.

Mixing int and float64

Here Go is stricter than maths. Two variables of different number types cannot be combined.

Open exercises/13/05-mixed-types.go. It does not compile:

# command-line-arguments
exercises\13\05-mixed-types.go:12:14: invalid operation: level * bonus (mismatched types int and float64)

level is an int and bonus is a float64. Fix the line with the conversion explained below.

A conversion makes a new value of the type you name. Write the type like a function:

level := 7
bonus := 1.5
fmt.Println(float64(level) * bonus) // 10.5

total := 7
count := 2
fmt.Println(total / count)                   // 3
fmt.Println(float64(total) / float64(count)) // 3.5

price := 3.99
fmt.Println(int(price)) // 3

Go makes you write this so that nothing changes type by accident. The last line shows why that matters: int(price) throws away the decimal part. It does not round, so 3.99 becomes 3. These lines are in exercises/13/04-conversions.go.

A number written directly in the code, such as 2 or 1.5, is more flexible than a variable. Go calls it a constant, and it takes whatever number type the calculation needs as long as its value fits. So bonus * 2 works although bonus is a float64 and 2 looks like an int. level * 1.5 does not work, because 1.5 cannot fit in an int:

1.5 (untyped float constant) truncated to int

Homework

Homework starters that need another package say so in their top comment. Remember that the Go extension for VS Code may add the import line for you when you save.

Problem 1 — Rectangle area

Open exercises/13/homework/01-rectangle-area.go. Two variables hold a rectangle's width and height. Compute the area and print it with a label, for example Area: 24.

Problem 2 — Floor and ceil

Open exercises/13/homework/02-floor-and-ceil.go. A variable holds the float64 3.7. Print it as it is, after math.Floor, and after math.Ceil. Add one comment line explaining the difference between floor and ceil.

Problem 3 — Roll two dice

Open exercises/13/homework/03-roll-two-dice.go. "Roll" two six-sided dice and print the results like this:

Die 1: 4
Die 2: 6
Total: 10

Use rand.Intn for the rolls. Roll each die separately so the two values are independent.

Challenge — Hypotenuse

Open exercises/13/homework/04-hypotenuse.go. Two int variables hold the two short sides of a right triangle (a and b). Compute and print the hypotenuse c using the Pythagorean theorem: c is the square root of a*a + b*b.

math.Sqrt takes a float64, so you need a conversion. Print all three values labelled. Show the hypotenuse with two decimal places using %.2f.

Stuck or finished? Open the homework solutions page.