Numeric Types
Sized integers, int versus int64, floats, and why Go refuses to mix two number types without a written conversion.
int8 int16 int32 int64 uint8 uint16 uint32 uint64 int uint float32 float64 — convert with T(v)Go has a separate type for each size of number, and the size is part of the name. int8, int16, int32 and int64 hold whole numbers that may be negative. uint8, uint16, uint32 and uint64 hold whole numbers that may not. The number in the name is how many bits of memory one value gets, and that fixes its range: an int8 runs from -128 to 127 and no further. float32 and float64 hold numbers with a fractional part.
int is the one you reach for
There is also a plain int, and a plain uint. The language specification says int is either 32 or 64 bits, decided by the machine the program is built for. On servers and laptops it is 64. Use int for counting and for positions in a list. Use a sized type only when something outside your program fixes the width, such as a file format or a database column.
Nothing converts on its own
Go never widens or narrows a number for you. An int and an int64 are two different types even on a machine where both are 64 bits, so adding one to the other is a compile error. You write the conversion yourself, as int64(count): the type name, then the value in brackets. That form is called a conversion and it works between any two numeric types.
What trips people up
Dividing two whole numbers gives a whole number. 7/2 is 3, not 3.5, and the remainder is thrown away rather than rounded. Converting a float to an integer cuts the fraction off towards zero, so int(19.99) is 19. Going past the top of a sized type wraps silently round to the bottom, with no error at run time.
Example
package main
import "fmt"
func main() {
// int is the default whole-number type. It is 64 bits on most machines.
var count int = 7
// int64 is always 64 bits, whatever the machine.
var total int64 = 100
// total + count does not compile: int64 and int are different types.
// int64(count) makes an int64 holding count's value.
sum := total + int64(count)
fmt.Println("sum:", sum)
// Whole-number division throws the remainder away. It does not round.
fmt.Println("7/2 =", 7/2)
fmt.Println("7.0/2.0 =", 7.0/2.0)
// Converting a float to an integer cuts the fraction off.
price := 19.99
fmt.Println("int(19.99) =", int(price))
// A sized type wraps round when it passes its largest value.
var small int8 = 127
small++
fmt.Println("127+1 as int8 =", small)
}
// Output:
// sum: 107
// 7/2 = 3
// 7.0/2.0 = 3.5
// int(19.99) = 19
// 127+1 as int8 = -128When to use it
- Pick int for a loop counter and int64 for a database id column that will outgrow 32 bits.
- Store a price as an int number of pennies so rounding never drifts.
- Check a value against math.MaxUint8 before writing it into a one-byte field of a wire format.
- Work out a success rate from two int counters without getting zero.
More examples
Unsigned types and the limits in math
An unsigned type has no room below zero, so subtracting past it wraps to the top of the range instead of going negative. The math package holds the limit of every sized type as a constant, which is how you check a value fits before you narrow it. Reach for this whenever a number arrives from outside your program and has to go into a smaller column or field.
package main
import (
"fmt"
"math"
)
func main() {
// Unsigned types hold no negative numbers. Going below zero wraps.
var age uint8 = 0
age--
fmt.Println("uint8 0-1 =", age)
// math names the limits so you do not have to remember them.
fmt.Println("largest int8:", math.MaxInt8)
fmt.Println("largest int64:", int64(math.MaxInt64))
// Check before narrowing, or the value is quietly mangled.
var big int64 = 300
if big > math.MaxUint8 {
fmt.Println(big, "does not fit in a uint8")
}
}
// Output:
// uint8 0-1 = 255
// largest int8: 127
// largest int64: 9223372036854775807
// 300 does not fit in a uint8Floats are approximations, so money is not a float
A float64 stores a binary fraction, and most decimal fractions have no exact binary form. Two numbers that should add to 0.3 land a hair above it. Compare floats by asking whether the gap is small enough rather than asking whether they are equal. For prices, count the smallest unit as a whole number and place the decimal point yourself when printing.
package main
import (
"fmt"
"math"
)
func main() {
// These must be variables. Go computes constant sums exactly.
a := 0.1
b := 0.2
sum := a + b
fmt.Println("0.1+0.2 =", sum)
fmt.Println("equal to 0.3?", sum == 0.3)
// Ask whether the gap is small enough instead.
const tolerance = 1e-9
fmt.Println("close to 0.3?", math.Abs(sum-0.3) < tolerance)
// Count pennies in an int, then place the point when you print.
pennies := 1999
fmt.Printf("price: %d.%02d\n", pennies/100, pennies%100)
}
// Output:
// 0.1+0.2 = 0.30000000000000004
// equal to 0.3? false
// close to 0.3? true
// price: 19.99Convert before you divide, not after
The commonest numeric bug in Go is working out a ratio from two int values. The division happens first, in whole numbers, and reaches zero before any conversion runs. Convert both sides to float64 first. This comes up in every rate, average and percentage you will ever compute from two counters.
package main
import "fmt"
func main() {
wins := 7
games := 12
// Both are int, so this is whole-number division: it is 0.
fmt.Println("integer ratio:", wins/games)
// Convert both sides first. Converting the answer afterwards is too late.
rate := float64(wins) / float64(games)
fmt.Printf("win rate: %.3f\n", rate)
// %% prints one literal percent sign.
fmt.Printf("win rate: %.1f%%\n", rate*100)
}
// Output:
// integer ratio: 0
// win rate: 0.583
// win rate: 58.3%
Discussion