Арифметические операции и приведение типов: особенности Go

Краткое описание

В этом уроке изучаем базовые арифметические операции в Go и ключевые отличия от других C-подобных языков. Разбираем целочисленное деление, остаток от деления, и самое важное — почему в Go нет автоматического приведения типов и как правильно работать с явным приведением (type casting).

Ключевые концепции

Пять основных операций

Go поддерживает стандартный набор арифметических операций:

var a int = 14
var b int = 5

sum := a + b           // сложение: 19
difference := a - b    // вычитание: 9
product := a * b       // умножение: 70
quotient := a / b      // деление: 2 (целочисленное!)
remainder := a % b     // остаток: 4

Важно: все операции — бинарные, то есть применяются к двум операндам.

Целочисленное деление — главная особенность

Ключевое отличие Go: если оба операнда целые числа, результат деления тоже будет целым числом (неполное частное).

var a int = 12
var b int = 5

result := a / b  // result = 2, а НЕ 2.4!

Как это работает

Вспоминаем деление столбиком из школы:

  12 : 5 = 2 (неполное частное)
  10
  --
   2 (остаток)

В целочисленном делении мы берём только неполное частное (цифра 2), а дробную часть отбрасываем.

Математическое объяснение:

  • 12 ÷ 5 = 2 (неполное частное)
  • 12 - (5 × 2) = 2 (остаток)
a := 13
b := 5

quotient := a / b    // 2 (неполное частное)
remainder := a % b   // 3 (остаток: 13 - 5×2 = 3)

Остаток от деления (модуль)

Оператор % возвращает остаток от целочисленного деления:

14 % 5  // 4  (14 = 5×2 + 4)
13 % 5  // 3  (13 = 5×2 + 3)
12 % 5  // 2  (12 = 5×2 + 2)
10 % 5  // 0  (10 = 5×2 + 0)

Формула: a % b = a - (a/b)*b

Важно: оператор % работает только с целыми числами. Для вещественных чисел используйте функцию math.Mod().

Отсутствие автоматического приведения типов

Главное отличие от C#, Java, JavaScript: в Go нет автоматического приведения типов в арифметических операциях.

В других языках (C#, Java)

int a = 10;
double b = 3.0;
var result = a / b;  // 3.333... (автоматически int → double)

В Go — ошибка компиляции

var a int = 10
var b float64 = 3.0

// result := a / b  // ОШИБКА! invalid operation: a / b (mismatched types int and float64)

Go требует явного приведения типов.

Явное приведение типов (Type Casting)

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

Приведение целого к вещественному

var a int = 14
var b int = 5

result := float64(a) / float64(b)  // 2.8
fmt.Printf("%.1f\n", result)

Или проще:

result := float64(a) / float64(b)

Приведение вещественного к целому

var x float64 = 5.0
var y int = 14

result := y / int(x)  // 2 (целочисленное деление)

Внимание: при приведении floatint дробная часть отбрасывается (округление в меньшую сторону):

int(5.0)  // 5
int(5.3)  // 5 (потеря данных!)
int(5.9)  // 5 (не 6!)

Тип результата

Тип результата арифметической операции всегда совпадает с типом операндов:

var a int = 10
var b int = 3

result1 := a / b  // result1 имеет тип int (= 3)

result2 := float64(a) / float64(b)  // result2 имеет тип float64 (= 3.333...)

Важно: даже если присваиваете результат в переменную другого типа, нужно явное приведение:

var result float64
var a int = 10
var b int = 3

// result = a / b  // ОШИБКА! cannot use a / b (value of type int) as float64

result = float64(a) / float64(b)  // правильно

Практические примеры

Полный пример базовых операций

package main

import "fmt"

func main() {
    fmt.Println("--- Основные операторы: +, -, *, /, % ---")

    var a int = 14
    var b int = 5

    // Сложение
    sum := a + b
    fmt.Printf("%d + %d = %d\n", a, b, sum)
    // 14 + 5 = 19

    // Вычитание
    difference := a - b
    fmt.Printf("%d - %d = %d\n", a, b, difference)
    // 14 - 5 = 9

    // Умножение
    product := a * b
    fmt.Printf("%d * %d = %d\n", a, b, product)
    // 14 * 5 = 70

    // Деление (целочисленное!)
    quotient := a / b
    fmt.Printf("%d / %d = %d (целочисленное)\n", a, b, quotient)
    // 14 / 5 = 2

    // Остаток от деления
    remainder := a % b
    fmt.Printf("%d %% %d = %d (остаток)\n", a, b, remainder)
    // 14 % 5 = 4
}

Приведение типов для обычного деления

var a int = 14
var b int = 5

// Вариант 1: приведение обоих к float64
result1 := float64(a) / float64(b)
fmt.Printf("%.2f\n", result1)  // 2.80

// Вариант 2: приведение только одного (достаточно)
result2 := float64(a) / float64(b)
fmt.Printf("%.2f\n", result2)  // 2.80

Смешивание типов (с явным приведением)

var intNum int = 14
var floatNum float64 = 5.0

// Деление int на float (приводим int к float64)
result1 := float64(intNum) / floatNum
fmt.Printf("%.1f\n", result1)  // 2.8

// Деление int на int (приводим float64 к int)
result2 := intNum / int(floatNum)  // целочисленное деление
remainder := intNum % int(floatNum)
fmt.Printf("Частное: %d, Остаток: %d\n", result2, remainder)
// Частное: 2, Остаток: 4

Приведение с потерей данных

var precise float64 = 5.3

// Приведение float → int отбрасывает дробную часть
asInt := int(precise)
fmt.Println(asInt)  // 5 (НЕ 6! Округление не происходит)

// Пример вычисления
var a int = 14
result := a / int(5.9)  // int(5.9) = 5, затем 14 / 5 = 2
fmt.Println(result)     // 2

Приведение между целочисленными типами

var bigInt int64 = 1000
var smallInt int8 = int8(bigInt)  // приведение int64 → int8

fmt.Println(smallInt)  // работает, если значение помещается

// Осторожно с переполнением!
var huge int64 = 300
var tiny int8 = int8(huge)  // переполнение! результат непредсказуем
fmt.Println(tiny)           // -44 (из-за переполнения)

Важные моменты

1. Целочисленное деление по умолчанию

Если оба операнда типа int, деление всегда целочисленное. Дробная часть отбрасывается, округление не происходит.

2. Нет автоматического приведения типов

В отличие от C#, Java, JavaScript — в Go нельзя смешивать типы без явного приведения. Это предотвращает ошибки и делает код более предсказуемым.

3. Оператор % только для целых чисел

14 % 5     // OK: 4
// 14.5 % 5.2  // ОШИБКА! invalid operation

Для вещественных чисел используйте math.Mod(x, y):

import "math"

result := math.Mod(14.5, 5.2)  // 4.1

4. Приведение float → int теряет дробную часть

int(3.14)   // 3
int(3.99)   // 3 (не 4!)
int(-2.9)   // -2

Это отбрасывание, а не округление. Для округления используйте math.Round(), math.Floor(), math.Ceil().

5. Тип результата = тип операндов

var a int = 10
var b int = 3

result := a / b  // тип result — int, значение 3

resultFloat := float64(a) / float64(b)  // тип float64, значение 3.333...

6. Проверяйте переполнение при приведении

var big int64 = 1000000
var small int8 = int8(big)  // переполнение! непредсказуемый результат

Компилятор не предупредит о потенциальном переполнении.

7. Оба операнда должны быть одного типа

var a int32 = 10
var b int64 = 5

// result := a / b  // ОШИБКА! mismatched types

result := int64(a) / b  // OK

Даже разные размеры int несовместимы без явного приведения.

8. Деление на ноль — паника

var a int = 10
var b int = 0

// result := a / b  // ПАНИКА! runtime error: integer divide by zero

Всегда проверяйте делитель перед операцией:

if b != 0 {
    result := a / b
}

Best Practices

1. Явное приведение для читаемости

Даже если приведение не обязательно, делайте его явным для ясности:

// Менее понятно
result := float64(a/b)  // сначала целочисленное деление, потом приведение

// Более понятно
result := float64(a) / float64(b)  // явное намерение: вещественное деление

2. Проверяйте деление на ноль

func safeDivide(a, b int) int {
    if b == 0 {
        return 0  // или возвращайте ошибку
    }
    return a / b
}

3. Используйте правильные функции для округления

import "math"

// Отбрасывание дробной части
truncated := int(3.9)  // 3

// Математическое округление
rounded := int(math.Round(3.9))  // 4

// Округление вниз
floored := int(math.Floor(3.9))  // 3

// Округление вверх
ceiled := int(math.Ceil(3.1))  // 4

4. Будьте осторожны с приведением к меньшим типам

var value int64 = 300

// Опасно без проверки
small := int8(value)  // переполнение возможно

// Безопаснее
if value >= math.MinInt8 && value <= math.MaxInt8 {
    small := int8(value)
}

5. Используйте константы для избежания приведений

const divisor = 5.0  // константа типизируется автоматически

var a int = 14
result := float64(a) / divisor  // divisor подстраивается под контекст

6. Документируйте потерю точности

// convertToInt приводит число к целому, отбрасывая дробную часть
func convertToInt(value float64) int {
    return int(value)  // дробная часть теряется
}

Что запомнить

  • Go поддерживает пять базовых арифметических операций: +, -, *, /, %
  • Деление целых чисел всегда целочисленное (неполное частное)
  • % возвращает остаток от деления (работает только с целыми числами)
  • В Go нет автоматического приведения типов (отличие от C#, Java, JS)
  • Для смешивания типов нужно явное приведение: float64(x), int(y)
  • Приведение float → int отбрасывает дробную часть (не округляет)
  • Тип результата операции всегда совпадает с типом операндов
  • Деление на ноль вызывает панику — всегда проверяйте делитель
  • Для вещественного остатка используйте math.Mod()
  • Для округления используйте math.Round(), math.Floor(), math.Ceil()
  • Приведение к меньшим типам может вызвать переполнение
  • Даже int32 и int64 несовместимы без явного приведения
  • Явное приведение делает код безопаснее и понятнее

Полезные ссылки