Numbers in Go
23 January 2025 / 5 min read
Working with numeric values in Golang is a bit more varied than in other programming languages such as TypeScript or PHP.
The reason for this is that we don’t just have one int
like in PHP but we have a dozen different types of integers.
Integer Types
Let’s have a look at the different integer types which Go offers, and then we’ll see what to use when.
We differentiate between signed & unsigned integer types:
Signed Integer Types
Type | Range |
---|---|
int8 | -128 to 127 |
int16 | -32768 to 32767 |
int32 | -2147483648 to 2147483647 |
int64 | -9223372036854775808 to 9223372036854775807 |
int | Platform-dependent (either int32 or int64 ) |
Unsigned Integer Types
Type | Range |
---|---|
uint8 | 0 to 255 |
uint16 | 0 to 65535 |
uint32 | 0 to 4294967295 |
uint64 | 0 to 18446744073709551615 |
uint | Platform-dependent (either uint32 or uint64 ) |
As you can already see, the difference between them is that signed integer types can represent both positive and negative numbers, whereas unsigned integer types can only represent non-negative numbers (zero and positive values).
The int
and uint
types in Go are platform-dependent, meaning their size is determined by the architecture of the system you’re running on:
- On a 32-bit system, it’s are 32 bits (4 bytes).
- On a 64-bit system, it’s 64 bits (8 bytes).
What to use when
In most cases you can just use int
. The int
type is typically the most efficient for the CPU to work with because it aligns with the native size of the machine. For example, on a 64-bit system, the CPU is optimized to handle 64-bit integers, so using int
(which is 64 bits on such systems) avoids unnecessary overhead.
Since int
is the default type for integer literals and is widely used in Go programs, it simplifies code readability and maintenance. You don’t need to worry about the exact size unless you have a specific reason to do so.
Using int
also makes your code portable across different architectures without requiring changes to the integer types.
That being said, if you do know for sure that some number will never exceed a certain amount, then you can use the corresponding type.
Let’s say you want to store a number that will tell us the amount of teams playing in the Premier League (20 teams), we can just use int8
or even uint8
because we know for sure that the number will be positive and never exceed the total capacity of uint8
.
Also when you are working with external systems or protocols that expect specific integer sizes (e.g., network protocols, binary file formats), you may need to use smaller types to ensure compatibility.
Zero Value
What happens if you declare an integer but don’t assign a value? Example:
var i int
In Go, every variable has a zero value, which is the default value assigned to a variable if it is declared but not explicitly initialized.
For all types of integers (int
, int8
, int16
, etc.) the zero value is always 0:
fmt.Println(i) // Output: 0
Doing Calculations
Go supports the standard arithmetic operations, which you can use to perform calculations with numbers. The basic arithmetic operators are:
- Addition (
+
): Adds two numbers. - Subtraction (
-
): Subtracts the second number from the first. - Multiplication (
*
): Multiplies two numbers. - Division (
/
): Divides the first number by the second. - Modulus (
%
): Returns the remainder of a division operation.
Here’s an example demonstrating these operations:
a := 10
b := 3
sum := a + b // 13
difference := a - b // 7
product := a * b // 30
quotient := a / b // 3
remainder := a % b // 1
fmt.Println(sum, difference, product, quotient, remainder)
Type Safety
In Go, you cannot directly perform arithmetic operations between two different integer types. Go is a statically typed language, which means the types of variables are checked at compile time, and mixing types in arithmetic operations is not allowed. Example:
var a int32 = 10
var b int64 = 20
result := a + b
fmt.Println(result)
This will fail when you try to build your project, resulting in the following error:
invalid operation: a + b (mismatched types int32 and int64)
Type Conversion
To perform arithmetic operations between different integer types, you must explicitly convert one of the operands to match the type of the other. This is done using type conversion:
var a int32 = 10
var b int64 = 20
// Convert a to int64 before performing the addition
result := int64(a) + b // Valid: result is of type int64
fmt.Println(result) // Output: 30
Rules for Type Conversion
-
Widening Conversions: Converting a smaller type to a larger type (e.g.,
int32
toint64
) is safe because no data is lost. -
Narrowing Conversions: Converting a larger type to a smaller type (e.g.,
int64
toint32
) can lead to data loss if the value exceeds the range of the smaller type. For example:
var a int64 = 50000
var b int32 = int32(a) // Safe: 50000 fits within int32 range
fmt.Println(b) // Output: 50000
var c int64 = 34359738368
var d int32 = int32(c) // Unsafe: value exceeds int32 range
fmt.Println(d) // Output: 0 (data loss occurs)
- Unsigned to Signed (and Vice Versa): Converting between signed and unsigned types requires caution, as negative values in signed types cannot be represented in unsigned types.
Aliases
Go provides two aliases for integer types that are commonly used in specific contexts:
byte
: An alias foruint8
, typically used to represent raw data or ASCII characters.rune
: An alias forint32
, typically used to represent a Unicode code point.
Here’s a brief example:
var b byte = 'A' // byte is an alias for uint8
var r rune = '世' // rune is an alias for int32
fmt.Println(b) // Output: 65
fmt.Println(r) // Output: 19990
This might seem confusing right now, but we’ll dive deeper into byte
and rune
in a future blog post, where we’ll explore their use cases in more detail, especially in the context of strings and text processing.