Introduction
Welcome Gophers to this new chapter. In this section, we will learn about numerical types. The content includes commonly used integer types, floating-point types, boolean types, as well as complex numbers and the literal value syntax introduced in version 1.13.
Knowledge Points:
- Integer types
- Floating-point types
- Boolean types
- Complex numbers
- Literal value syntax
Integers
Integers can be broadly divided into two categories: unsigned integers and signed integers. Signed integers are the most widely used.
Unsigned means that it can only represent non-negative numbers (0 and positive numbers), while signed numbers can represent both negative and non-negative numbers.
Unsigned integers can be divided into four sizes: 8 bits, 16 bits, 32 bits, and 64 bits, represented by uint8, uint16, uint32, and uint64, respectively. The corresponding signed integers are int8, int16, int32, and int64. The following table shows the different ranges represented by each type:
| Type | Description | Range |
|---|---|---|
| uint8 | 8-bit unsigned int | 0 to 255 |
| int8 | 8-bit signed int | -128 to 127 |
| uint16 | 16-bit unsigned int | 0 to 65535 |
| int16 | 16-bit signed int | -32768 to 32767 |
| uint32 | 32-bit unsigned int | 0 to 4294967295 |
| int32 | 32-bit signed int | -2147483648 to 2147483647 |
| uint64 | 64-bit unsigned int | 0 to 18446744073709551615 |
| int64 | 64-bit signed int | -9223372036854775808 to 9223372036854775807 |
Let's take uint8 and int8 as examples. They are both 8-bit integers and can represent 256 values. In the unsigned integer type uint8, the range it can represent is from 0 to 255, while in the signed integer type int8, the range it can represent is from -128 to 127.
In addition to the above 8 types, there are three other special integer types, uint, int, and uintptr, where uint and int may represent different ranges on different platforms, and uintptr is used for storing pointer addresses.
| Type | Range |
|---|---|
uint |
uint32 on 32-bit systems, uint64 on 64-bit systems |
int |
int32 on 32-bit systems, int64 on 64-bit systems |
uintptr |
Unsigned integer type used for storing pointer addresses, mostly used in low-level programming like unsafe operations |
Now, let's create a file named integer.go to demonstrate the use of integers:
cd ~/project
touch integer.go
package main
import (
"fmt"
"unsafe"
)
func main() {
// View the type of int in the current environment
// Declare a as the type int
var a int
// Use unsafe.Sizeof() to output the memory size occupied by the type
fmt.Printf("The type int in the current environment is %d bits\n", unsafe.Sizeof(a)*8)
var b int8 = 125
// Use the %d placeholder in fmt.Printf to output the value of the integer
// Use the %T placeholder in fmt.Printf to output the type of the variable
fmt.Printf("The value of b is %d, and the type is %T\n", b, b)
// Integer operations
// Declare integers c and d, and calculate their sum
c, d := 2, 3
fmt.Printf("c + d = %d\n", c+d)
// 10 - 5
fmt.Printf("10 - 5 = %d\n", 10-5)
// 8 * 10
fmt.Printf("8 * 10 = %d\n", 8*10)
// Example of uintptr usage - storing an address
var ptr uintptr
x := 42
// Convert the pointer to x to uintptr
ptr = uintptr(unsafe.Pointer(&x))
fmt.Printf("The address of x stored in ptr is: %v\n", ptr)
}
After executing the program, we get the following output:
go run integer.go
The type int in the current environment is 64 bits
The value of b is 125, and the type is int8
c + d = 5
10 - 5 = 5
8 * 10 = 80
The address of x stored in ptr is: 824634818784
Explanation of the output:
The type int in the current environment is 64 bits: This line shows that theinttype on the system where the code is being run is 64 bits (or 8 bytes), indicating it is anint64. Theunsafe.Sizeof(a)returns the size of variableain bytes, and multiplying by 8 converts it to bits. This means that theinttype can hold larger integer values.The value of b is 125, and the type is int8: Here, we have declared a variablebof typeint8and assigned it the value 125. The output confirms this by showing both the value and the data type.c + d = 5,10 - 5 = 5,8 * 10 = 80: These lines showcase basic integer arithmetic operations: addition, subtraction, and multiplication. The output confirms the correct results of these calculations.The address of x stored in ptr is: 824634818784: This demonstrates howuintptrcan be used to store a memory address. We're converting a pointer to the integer variablexto auintptrtype. The actual address value will vary each time the program runs. This is an advanced use case typically found in unsafe operations and system programming.
In this file, the unsafe.Sizeof() function can be used to obtain the number of bytes occupied by the current variable type. 1 byte (byte) is equal to 8 bits, so unsafe.Sizeof()*8 can obtain the number of bits occupied by the type. From the output, we can see that the online environment is 64 bits. The actual type of int in the online environment is int64.
We can use the following command in the terminal to determine the current system architecture:
dpkg --print-architecture
amd64
Floating-Point Numbers
Floating-point numbers represent real numbers with fractional parts in Go using two types: float32 and float64. The default floating-point type is float64.
float32 and float64 represent different precisions. The default precision of float64 is higher than that of float32.
The IEEE 754 standard is the most widely used floating-point calculation standard in computers, and modern CPUs all support this standard. Like other programming languages, Go also uses IEEE 754 to store floating-point numbers.
We know that a byte in a computer can store 8 bits. float32 is a single-precision floating-point number and occupies 4 bytes, which is 32 bits. float64 is a double-precision and occupies 8 bytes, which is 64 bits.
In float32, the sign bit occupies 1 bit, the exponent occupies 8 bits, and the remaining 23 bits are used to represent the mantissa.
In float64, the sign bit also occupies 1 bit, the exponent occupies 11 bits, and the remaining 52 bits are used to represent the mantissa.
The maximum value that float32 can represent is approximately 3.4e+38 in scientific notation, and the minimum value is 1.4e-45. The maximum value that float64 can represent is approximately 1.8e+308, and the minimum value is 4.9e-324. We can see that the range of floating-point values can be from very small to very large.
We can use the constant math.MaxFloat32 to represent the maximum value in float32. Use the constant math.MaxFloat64 to represent the maximum value in float64.
Representation of Floating-Point Numbers
When outputting floating-point numbers, we can use the %f placeholder of the fmt package's Printf function. Here is an example:
cd ~/project
touch float.go
package main
import (
"fmt"
"math"
)
func main() {
// Output without exponential form
fmt.Printf("2.333 without exponential form: %f\n", 2.333)
fmt.Printf("Pi without exponential form: %f\n", math.Pi)
// Use %.2f to keep two decimal places for Pi
fmt.Printf("Pi with two decimal places: %.2f\n", math.Pi)
fmt.Printf("The maximum value of float32: %f\n", math.MaxFloat32)
// Exponential form
fmt.Printf("2.333 in exponential form: %e", 2.333)
}
Run the command and you'll see the following output.
go run float.go
2.333 without exponential form: 2.333000
Pi without exponential form: 3.141593
Pi with two decimal places: 3.14
The maximum value of float32: 340282346638528859811704183484516925440.000000
2.333 in exponential form: 2.333000e+00
Explanation of the output:
2.333 without exponential form: 2.333000: This shows the number2.333printed using the%fplaceholder. By default, the%fplaceholder shows 6 digits after the decimal point, so2.333becomes2.333000.Pi without exponential form: 3.141593: This line prints the value of the constantmath.Pi, which is an approximation of the mathematical constant π. The%fplaceholder displays it with full precision.Pi with two decimal places: 3.14: By using%.2f, we tellfmt.Printfto format the number to two digits after the decimal point, which results in3.14. This is very useful when you only need a certain precision for the output.The maximum value of float32: 340282346638528859811704183484516925440.000000: This shows the maximum value that afloat32can represent. Note that it's a very large number, and when printed with%f, it will be represented with many digits.2.333 in exponential form: 2.333000e+00: This line shows how to represent a floating-point number in exponential (scientific) notation using the%eplaceholder. Thee+00at the end indicates that we multiply2.333000by 10 to the power of 0, which is simply2.333. If the exponent wase+02, then the number would be multiplied by10^2(100), resulting in233.3.
Boolean Types
The bool type has only two possible values: true or false, with false being the default. It has the following characteristics:
- It cannot be converted to other types, such as converting an integer to a boolean or converting a boolean to an integer.
- It cannot participate in arithmetic operations.
Boolean types are usually used in conjunction with relational operators, such as =, >, and <. Let's create a file named bool.go to see a demonstration:
cd ~/project
touch bool.go
package main
import (
"fmt"
)
func main() {
// Use the %t placeholder in fmt.Printf to represent a boolean value
fmt.Printf("Is 3 equal to 2? %t\n", 3 == 2)
fmt.Printf("Is 2 equal to 2? %t\n", 2 == 2)
// Determine whether a and b are equal
a, b := 1, 2
fmt.Printf("a is %d, b is %d\n", a, b)
if a > b {
fmt.Println("a is greater than b")
} else if a == b {
fmt.Println("a is equal to b")
} else {
fmt.Println("b is greater than a")
}
}
After running the program, we get the following output:
go run bool.go
Is 3 equal to 2? false
Is 2 equal to 2? true
a is 1, b is 2
b is greater than a
Explanation of the output:
Is 3 equal to 2? false: The expression3 == 2evaluates tofalse, which is then printed using the%tplaceholder.Is 2 equal to 2? true: The expression2 == 2evaluates totrue, which is then printed using the%tplaceholder.a is 1, b is 2: This line prints the assigned values of the integer variablesaandb.b is greater than a: Theif-else if-elseconditional statement evaluates toa < b, which is1 < 2(true), therefore theelsestatement is executed, resulting in the program printing "b is greater than a".
In this program, we first use %t in fmt.Printf to represent a boolean value, and then use the if statement to determine the relationship between two values. In fmt.Printf, we can use the %d placeholder to represent an integer, the %f placeholder to represent a floating-point number, and the %t placeholder to represent a boolean value.
Complex Numbers
Go also has built-in complex number types, which can be divided into complex64 and complex128 types. In complex64, both the real and imaginary parts are 32-bit, while in complex128, both the real and imaginary parts are 64-bit. We can easily perform complex number operations in Go.
Create a file named complex.go and enter the following code:
cd ~/project
touch complex.go
package main
import (
"fmt"
)
func main() {
// Initialize complex numbers in different ways
c1 := complex(3, 1)
c2 := 4 + 5i
// Complex number operations
c3 := c1 + c2
c4 := c1 * c2
// Use the real() function to obtain the real part of a complex number, and the imag() function to obtain the imaginary part of a complex number
fmt.Printf("The real part of c1 is %v, the imaginary part is %v\n", real(c1), imag(c1))
// %v in fmt.Printf can be used to represent complex numbers
fmt.Printf("c1 + c2 is %v\n", c3)
fmt.Printf("c1 * c2 is %v\n", c4)
}
After running the program, we get the following output:
go run complex.go
The real part of c1 is 3, the imaginary part is 1
c1 + c2 is (7+6i)
c1 * c2 is (7+19i)
Explanation of the output:
The real part of c1 is 3, the imaginary part is 1: The functionreal(c1)extracts the real part of the complex numberc1, which is3, andimag(c1)extracts the imaginary part ofc1, which is1. These values are then printed using the%vplaceholder.c1 + c2 is (7+6i): This line shows the result of adding complex numbersc1andc2.c1is defined as3 + 1iandc2as4 + 5i. Adding the real and imaginary parts separately gives(3 + 4) + (1 + 5)i, or7 + 6i.c1 * c2 is (7+19i): This line shows the result of multiplying complex numbersc1andc2.(3 + 1i) * (4 + 5i)is calculated as(3*4 - 1*5) + (3*5 + 1*4)i, which simplifies to(12-5) + (15+4)i, and finally to7+19i.
In this program, we demonstrated how to initialize and perform operations on complex numbers using the complex function and the + and * operators. real() and imag() are used to extract the real and imaginary parts of a complex number respectively. The %v verb in fmt.Printf is used as a general placeholder to display complex numbers.
Literal Value Syntax
In version 1.13, Go introduced the Numeric Literal Syntax. It defines the representation of numbers in different number systems. Let's take a look at its rules.
- Binary: Add
0bbefore the integer. For example,0b101is equivalent to5in decimal. - Octal: Add
0oor0Obefore the integer. For example,0o11is equivalent to9in decimal. - Hexadecimal: Add
0xor0Xbefore the integer. For example,0x1bis equivalent to27in decimal. - Use
_to separate digits in the integer, for example,0b1000_0100_0010_0001is equivalent to0b1000010000100001. This can improve readability.
Let's demonstrate it in detail:
cd ~/project
touch literals.go
package main
import "fmt"
func main() {
// Binary, add 0b at the beginning
var a int = 0b101
fmt.Printf("Binary a is %b, decimal is %d\n", a, a)
// Octal, add 0o or 0O at the beginning
var b int = 0o11
fmt.Printf("Octal b is %o, decimal is %d\n", b, b)
// Hexadecimal, add 0x or 0X at the beginning
var c int = 0x1b
fmt.Printf("Hexadecimal c is %x, decimal is %d\n", c, c)
// Use separators
d := 0b1000_0100_0010_0001
e := 0b1000010000100001
if d == e {
fmt.Println("d is equal to e")
}
}
After running the program, we get the following output:
go run literals.go
Binary a is 101, decimal is 5
Octal b is 11, decimal is 9
Hexadecimal c is 1b, decimal is 27
d is equal to e
Explanation of the output:
Binary a is 101, decimal is 5: This line shows the binary representation of0b101which is101, and its decimal equivalent is5. The%bplaceholder displays the binary value ofa, while%ddisplays the decimal representation.Octal b is 11, decimal is 9: The octal number0o11is equivalent to1*8^1 + 1*8^0 = 8 + 1 = 9in decimal. The%oplaceholder displays the octal representation ofb, and%ddisplays its decimal value.Hexadecimal c is 1b, decimal is 27: The hexadecimal number0x1bis equivalent to1*16^1 + 11*16^0 = 16 + 11 = 27in decimal. The%xplaceholder displays the hexadecimal representation ofc, and%ddisplays its decimal value.d is equal to e: This shows that the binary literal0b1000_0100_0010_0001is equal to0b1000010000100001in value. The underscores are purely for readability and do not change the number's value. Theifcondition correctly evaluates totrue.
In this program, we demonstrated the declaration and output of different number systems, as well as the use of separators. In fmt.Printf, we used different placeholders to represent different number systems. For example, %b represents binary and %x represents hexadecimal. Mastering them will improve programming efficiency.
Summary
Let's review what we have learned in this section:
- Integers can be divided into signed integers and unsigned integers
- The default integer types
intanduinthave ranges that depend on the platform - Representation of floating-point numbers
- How to use complex numbers
- Introduction of literal value syntax
- How to use different placeholders
In this section, we explained and demonstrated common integer types, floating-point types, and boolean types. We discussed their sizes, ranges, and usage. We also introduced complex numbers and literal constants. Numerical types are the cornerstone of Go programs, and it is important for learners to study them well.



