Числовые типы в Golang

GolangBeginner
Практиковаться сейчас

Введение

Добро пожаловать, Gophers, в эту новую главу. В этом разделе мы изучим числовые типы. Содержание включает в себя часто используемые целочисленные типы (integer types), типы с плавающей точкой (floating-point types), логические типы (boolean types), а также комплексные числа и синтаксис литеральных значений (literal value syntax), представленный в версии 1.13.

Ключевые моменты:

  • Целочисленные типы (Integer types)
  • Типы с плавающей точкой (Floating-point types)
  • Логические типы (Boolean types)
  • Комплексные числа (Complex numbers)
  • Синтаксис литеральных значений (Literal value syntax)

Целые числа (Integers)

Целые числа можно разделить на две категории: беззнаковые целые числа (unsigned integers) и знаковые целые числа (signed integers). Знаковые целые числа используются наиболее широко.

Беззнаковое (Unsigned) означает, что оно может представлять только неотрицательные числа (0 и положительные числа), в то время как знаковые числа (signed numbers) могут представлять как отрицательные, так и неотрицательные числа.

Беззнаковые целые числа можно разделить на четыре размера: 8 бит, 16 бит, 32 бита и 64 бита, представленные соответственно uint8, uint16, uint32 и uint64. Соответствующие знаковые целые числа: int8, int16, int32 и int64. В следующей таблице показаны различные диапазоны, представленные каждым типом:

Тип Описание Диапазон
uint8 8-битное беззнаковое int от 0 до 255
int8 8-битное знаковое int от -128 до 127
uint16 16-битное беззнаковое int от 0 до 65535
int16 16-битное знаковое int от -32768 до 32767
uint32 32-битное беззнаковое int от 0 до 4294967295
int32 32-битное знаковое int от -2147483648 до 2147483647
uint64 64-битное беззнаковое int от 0 до 18446744073709551615
int64 64-битное знаковое int от -9223372036854775808 до 9223372036854775807

Возьмем в качестве примера uint8 и int8. Они оба являются 8-битными целыми числами и могут представлять 256 значений. В беззнаковом целочисленном типе uint8 диапазон, который он может представлять, составляет от 0 до 255, в то время как в знаковом целочисленном типе int8 диапазон, который он может представлять, составляет от -128 до 127.

В дополнение к вышеуказанным 8 типам существуют три других специальных целочисленных типа: uint, int и uintptr, где uint и int могут представлять разные диапазоны на разных платформах, а uintptr используется для хранения адресов указателей (pointer addresses).

Тип Диапазон
uint uint32 в 32-битных системах, uint64 в 64-битных системах
int int32 в 32-битных системах, int64 в 64-битных системах
uintptr Беззнаковый целочисленный тип, используемый для хранения адресов указателей (pointer addresses), в основном используется в низкоуровневом программировании, таком как небезопасные операции (unsafe operations)

Теперь давайте создадим файл с именем integer.go, чтобы продемонстрировать использование целых чисел:

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)
}

После выполнения программы мы получим следующий вывод:

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

Объяснение вывода:

  • The type int in the current environment is 64 bits: Эта строка показывает, что тип int в системе, где выполняется код, составляет 64 бита (или 8 байт), что указывает на то, что это int64. unsafe.Sizeof(a) возвращает размер переменной a в байтах, а умножение на 8 преобразует его в биты. Это означает, что тип int может содержать большие целочисленные значения.
  • The value of b is 125, and the type is int8: Здесь мы объявили переменную b типа int8 и присвоили ей значение 125. Вывод подтверждает это, показывая как значение, так и тип данных.
  • c + d = 5, 10 - 5 = 5, 8 * 10 = 80: Эти строки демонстрируют основные арифметические операции с целыми числами: сложение, вычитание и умножение. Вывод подтверждает правильные результаты этих вычислений.
  • The address of x stored in ptr is: 824634818784: Это демонстрирует, как uintptr можно использовать для хранения адреса памяти. Мы преобразуем указатель на целочисленную переменную x в тип uintptr. Фактическое значение адреса будет меняться каждый раз при запуске программы. Это продвинутый вариант использования, обычно встречающийся в небезопасных операциях (unsafe operations) и системном программировании.

В этом файле функция unsafe.Sizeof() может использоваться для получения количества байтов, занимаемых текущим типом переменной. 1 байт (byte) равен 8 битам, поэтому unsafe.Sizeof()*8 может получить количество битов, занимаемых типом. Из вывода мы видим, что онлайн-среда составляет 64 бита. Фактический тип int в онлайн-среде — int64.

Мы можем использовать следующую команду в терминале, чтобы определить текущую архитектуру системы:

dpkg --print-architecture
amd64

Числа с плавающей точкой (Floating-Point Numbers)

Числа с плавающей точкой (floating-point numbers) представляют вещественные числа с дробными частями в Go, используя два типа: float32 и float64. Типом с плавающей точкой по умолчанию является float64.

float32 и float64 представляют различную точность (precision). Точность float64 по умолчанию выше, чем у float32.

Стандарт IEEE 754 является наиболее широко используемым стандартом вычислений с плавающей точкой в компьютерах, и современные процессоры (CPU) все поддерживают этот стандарт. Как и другие языки программирования, Go также использует IEEE 754 для хранения чисел с плавающей точкой.

Мы знаем, что байт (byte) в компьютере может хранить 8 бит. float32 — это число с плавающей точкой одинарной точности (single-precision floating-point number) и занимает 4 байта, что составляет 32 бита. float64 — это число двойной точности (double-precision) и занимает 8 байт, что составляет 64 бита.

В float32 знаковый бит (sign bit) занимает 1 бит, экспонента (exponent) занимает 8 бит, а оставшиеся 23 бита используются для представления мантиссы (mantissa).

В float64 знаковый бит также занимает 1 бит, экспонента занимает 11 бит, а оставшиеся 52 бита используются для представления мантиссы.

Максимальное значение, которое может представлять float32, составляет приблизительно 3.4e+38 в научной нотации (scientific notation), а минимальное значение — 1.4e-45. Максимальное значение, которое может представлять float64, составляет приблизительно 1.8e+308, а минимальное значение — 4.9e-324. Мы видим, что диапазон значений с плавающей точкой может быть от очень малого до очень большого.

Мы можем использовать константу math.MaxFloat32 для представления максимального значения в float32. Используйте константу math.MaxFloat64 для представления максимального значения в float64.

Представление чисел с плавающей точкой

При выводе чисел с плавающей точкой мы можем использовать заполнитель %f функции Printf пакета fmt. Вот пример:

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)
}

Запустите команду, и вы увидите следующий вывод.

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

Объяснение вывода:

  • 2.333 without exponential form: 2.333000: Это показывает число 2.333, напечатанное с использованием заполнителя %f. По умолчанию заполнитель %f показывает 6 цифр после десятичной точки, поэтому 2.333 становится 2.333000.
  • Pi without exponential form: 3.141593: Эта строка печатает значение константы math.Pi, которая является приближением математической константы π. Заполнитель %f отображает ее с полной точностью.
  • Pi with two decimal places: 3.14: Используя %.2f, мы указываем fmt.Printf отформатировать число до двух цифр после десятичной точки, что приводит к 3.14. Это очень полезно, когда вам нужна только определенная точность для вывода.
  • The maximum value of float32: 340282346638528859811704183484516925440.000000: Это показывает максимальное значение, которое может представлять float32. Обратите внимание, что это очень большое число, и при печати с %f оно будет представлено множеством цифр.
  • 2.333 in exponential form: 2.333000e+00: Эта строка показывает, как представить число с плавающей точкой в экспоненциальной (научной) нотации (exponential (scientific) notation) с использованием заполнителя %e. e+00 в конце указывает, что мы умножаем 2.333000 на 10 в степени 0, что просто 2.333. Если бы экспонента была e+02, то число было бы умножено на 10^2 (100), что привело бы к 233.3.

Логические типы (Boolean Types)

Тип bool имеет только два возможных значения: true или false, причем false является значением по умолчанию. Он имеет следующие характеристики:

  • Его нельзя преобразовать в другие типы, например, преобразовать целое число в логическое или преобразовать логическое значение в целое число.
  • Он не может участвовать в арифметических операциях.

Логические типы обычно используются в сочетании с операторами отношения (relational operators), такими как =, >, и <. Давайте создадим файл с именем bool.go, чтобы увидеть демонстрацию:

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")
    }
}

После запуска программы мы получим следующий вывод:

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

Объяснение вывода:

  • Is 3 equal to 2? false: Выражение 3 == 2 вычисляется как false, которое затем выводится с использованием заполнителя %t.
  • Is 2 equal to 2? true: Выражение 2 == 2 вычисляется как true, которое затем выводится с использованием заполнителя %t.
  • a is 1, b is 2: Эта строка выводит присвоенные значения целочисленных переменных a и b.
  • b is greater than a: Условный оператор if-else if-else вычисляется как a < b, что равно 1 < 2 (истина), поэтому выполняется оператор else, в результате чего программа печатает "b is greater than a".

В этой программе мы сначала используем %t в fmt.Printf для представления логического значения, а затем используем оператор if для определения взаимосвязи между двумя значениями. В fmt.Printf мы можем использовать заполнитель %d для представления целого числа, заполнитель %f для представления числа с плавающей точкой и заполнитель %t для представления логического значения.

Комплексные числа (Complex Numbers)

Go также имеет встроенные типы комплексных чисел (complex number types), которые можно разделить на типы complex64 и complex128. В complex64 как действительная (real), так и мнимая (imaginary) части являются 32-битными, в то время как в complex128 как действительная, так и мнимая части являются 64-битными. Мы можем легко выполнять операции с комплексными числами в Go.

Создайте файл с именем complex.go и введите следующий код:

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)
}

После запуска программы мы получим следующий вывод:

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)

Объяснение вывода:

  • The real part of c1 is 3, the imaginary part is 1: Функция real(c1) извлекает действительную часть (real part) комплексного числа c1, которая равна 3, а imag(c1) извлекает мнимую часть (imaginary part) c1, которая равна 1. Эти значения затем выводятся с использованием заполнителя %v.
  • c1 + c2 is (7+6i): Эта строка показывает результат сложения комплексных чисел c1 и c2. c1 определено как 3 + 1i, а c2 как 4 + 5i. Сложение действительных и мнимых частей по отдельности дает (3 + 4) + (1 + 5)i, или 7 + 6i.
  • c1 * c2 is (7+19i): Эта строка показывает результат умножения комплексных чисел c1 и c2. (3 + 1i) * (4 + 5i) вычисляется как (3*4 - 1*5) + (3*5 + 1*4)i, что упрощается до (12-5) + (15+4)i, и, наконец, до 7+19i.

В этой программе мы продемонстрировали, как инициализировать и выполнять операции над комплексными числами с использованием функции complex и операторов + и *. real() и imag() используются для извлечения действительной и мнимой частей комплексного числа соответственно. Глагол %v в fmt.Printf используется в качестве общего заполнителя для отображения комплексных чисел.

Синтаксис литеральных значений (Literal Value Syntax)

В версии 1.13 Go представил Numeric Literal Syntax (синтаксис числовых литералов). Он определяет представление чисел в различных системах счисления. Давайте рассмотрим его правила.

  • Двоичная (Binary): Добавьте 0b перед целым числом. Например, 0b101 эквивалентно 5 в десятичной системе.
  • Восьмеричная (Octal): Добавьте 0o или 0O перед целым числом. Например, 0o11 эквивалентно 9 в десятичной системе.
  • Шестнадцатеричная (Hexadecimal): Добавьте 0x или 0X перед целым числом. Например, 0x1b эквивалентно 27 в десятичной системе.
  • Используйте _ для разделения цифр в целых числах, например, 0b1000_0100_0010_0001 эквивалентно 0b1000010000100001. Это может улучшить читаемость.

Давайте продемонстрируем это подробно:

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")
    }
}

После запуска программы мы получим следующий вывод:

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

Объяснение вывода:

  • Binary a is 101, decimal is 5: Эта строка показывает двоичное представление 0b101, которое равно 101, а его десятичный эквивалент равен 5. Заполнитель %b отображает двоичное значение a, а %d отображает десятичное представление.
  • Octal b is 11, decimal is 9: Восьмеричное число 0o11 эквивалентно 1*8^1 + 1*8^0 = 8 + 1 = 9 в десятичной системе. Заполнитель %o отображает восьмеричное представление b, а %d отображает его десятичное значение.
  • Hexadecimal c is 1b, decimal is 27: Шестнадцатеричное число 0x1b эквивалентно 1*16^1 + 11*16^0 = 16 + 11 = 27 в десятичной системе. Заполнитель %x отображает шестнадцатеричное представление c, а %d отображает его десятичное значение.
  • d is equal to e: Это показывает, что двоичный литерал (binary literal) 0b1000_0100_0010_0001 равен 0b1000010000100001 по значению. Подчеркивания используются исключительно для читаемости и не изменяют значение числа. Условие if правильно вычисляется как true.

В этой программе мы продемонстрировали объявление и вывод различных систем счисления, а также использование разделителей. В fmt.Printf мы использовали разные заполнители для представления разных систем счисления. Например, %b представляет двоичную систему, а %x представляет шестнадцатеричную систему. Освоение их повысит эффективность программирования.

Резюме

Давайте повторим, что мы изучили в этом разделе:

  • Целые числа (integers) можно разделить на знаковые (signed integers) и беззнаковые (unsigned integers)
  • Диапазоны типов целых чисел int и uint по умолчанию зависят от платформы
  • Представление чисел с плавающей точкой (floating-point numbers)
  • Как использовать комплексные числа (complex numbers)
  • Введение в синтаксис литеральных значений (literal value syntax)
  • Как использовать различные заполнители (placeholders)

В этом разделе мы объяснили и продемонстрировали распространенные типы целых чисел, типы с плавающей точкой и логические типы (boolean types). Мы обсудили их размеры, диапазоны и использование. Мы также представили комплексные числа и литеральные константы (literal constants). Числовые типы являются краеугольным камнем программ Go, и учащимся важно хорошо их изучить.