Numerical Types in Golang

GolangGolangBeginner
Practice Now

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
This is a Guided Lab, which provides step-by-step instructions to help you learn and practice. Follow the instructions carefully to complete each step and gain hands-on experience. Historical data shows that this is a beginner level lab with a 91.3% completion rate. It has received a 94.12% positive review rate from learners.

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("Golang")) -.-> go/BasicsGroup(["Basics"]) go(("Golang")) -.-> go/FunctionsandControlFlowGroup(["Functions and Control Flow"]) go(("Golang")) -.-> go/AdvancedTopicsGroup(["Advanced Topics"]) go/BasicsGroup -.-> go/values("Values") go/BasicsGroup -.-> go/variables("Variables") go/FunctionsandControlFlowGroup -.-> go/if_else("If Else") go/AdvancedTopicsGroup -.-> go/number_parsing("Number Parsing") go/AdvancedTopicsGroup -.-> go/base64_encoding("base64 Encoding") subgraph Lab Skills go/values -.-> lab-149067{{"Numerical Types in Golang"}} go/variables -.-> lab-149067{{"Numerical Types in Golang"}} go/if_else -.-> lab-149067{{"Numerical Types in Golang"}} go/number_parsing -.-> lab-149067{{"Numerical Types in Golang"}} go/base64_encoding -.-> lab-149067{{"Numerical Types in Golang"}} end

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 the int type on the system where the code is being run is 64 bits (or 8 bytes), indicating it is an int64. The unsafe.Sizeof(a) returns the size of variable a in bytes, and multiplying by 8 converts it to bits. This means that the int type can hold larger integer values.
  • The value of b is 125, and the type is int8: Here, we have declared a variable b of type int8 and 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 how uintptr can be used to store a memory address. We're converting a pointer to the integer variable x to a uintptr type. 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 number 2.333 printed using the %f placeholder. By default, the %f placeholder shows 6 digits after the decimal point, so 2.333 becomes 2.333000.
  • Pi without exponential form: 3.141593: This line prints the value of the constant math.Pi, which is an approximation of the mathematical constant π. The %f placeholder displays it with full precision.
  • Pi with two decimal places: 3.14: By using %.2f, we tell fmt.Printf to format the number to two digits after the decimal point, which results in 3.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 a float32 can 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 %e placeholder. The e+00 at the end indicates that we multiply 2.333000 by 10 to the power of 0, which is simply 2.333. If the exponent was e+02, then the number would be multiplied by 10^2 (100), resulting in 233.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 expression 3 == 2 evaluates to false, which is then printed using the %t placeholder.
  • Is 2 equal to 2? true: The expression 2 == 2 evaluates to true, which is then printed using the %t placeholder.
  • a is 1, b is 2: This line prints the assigned values of the integer variables a and b.
  • b is greater than a: The if-else if-else conditional statement evaluates to a < b, which is 1 < 2 (true), therefore the else statement 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 function real(c1) extracts the real part of the complex number c1, which is 3, and imag(c1) extracts the imaginary part of c1, which is 1. These values are then printed using the %v placeholder.
  • c1 + c2 is (7+6i): This line shows the result of adding complex numbers c1 and c2. c1 is defined as 3 + 1i and c2 as 4 + 5i. Adding the real and imaginary parts separately gives (3 + 4) + (1 + 5)i, or 7 + 6i.
  • c1 * c2 is (7+19i): This line shows the result of multiplying complex numbers c1 and c2. (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 to 7+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 0b before the integer. For example, 0b101 is equivalent to 5 in decimal.
  • Octal: Add 0o or 0O before the integer. For example, 0o11 is equivalent to 9 in decimal.
  • Hexadecimal: Add 0x or 0X before the integer. For example, 0x1b is equivalent to 27 in decimal.
  • Use _ to separate digits in the integer, for example, 0b1000_0100_0010_0001 is equivalent to 0b1000010000100001. 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 of 0b101 which is 101, and its decimal equivalent is 5. The %b placeholder displays the binary value of a, while %d displays the decimal representation.
  • Octal b is 11, decimal is 9: The octal number 0o11 is equivalent to 1*8^1 + 1*8^0 = 8 + 1 = 9 in decimal. The %o placeholder displays the octal representation of b, and %d displays its decimal value.
  • Hexadecimal c is 1b, decimal is 27: The hexadecimal number 0x1b is equivalent to 1*16^1 + 11*16^0 = 16 + 11 = 27 in decimal. The %x placeholder displays the hexadecimal representation of c, and %d displays its decimal value.
  • d is equal to e: This shows that the binary literal 0b1000_0100_0010_0001 is equal to 0b1000010000100001 in value. The underscores are purely for readability and do not change the number's value. The if condition correctly evaluates to true.

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 int and uint have 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.