День 03 · Типы и арифметика · страница 6 из 6

Деление на ноль и переполнение

До сих пор ошибка значила «программа не собралась». Сегодня — ошибка другого рода: программа собралась, запустилась и упала посреди работы. В Go это паника. Разберём, откуда она берётся при делении и как читать её сообщение.


Деление на ноль, которое видит компилятор

Код для чтения · разбираем, набирать не нужно
	fmt.Println(120 / 0)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:6:20: invalid operation: division by zero

На ноль делить нельзя, и компилятор это видит: ноль записан в коде. То же, если слева переменная, а справа константа:

Код для чтения · разбираем, набирать не нужно
	orders := 120
	fmt.Println(orders / 0)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:7:23: invalid operation: division by zero

То же и с именованной константой из дня 02 (const couriers = 0): её значение компилятору известно.


Паника: деление на ноль во время работы

Ситуация. Заказы надо поровну раздать курьерам, а курьеров ноль.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func main() {
	orders := 120
	couriers := 0
	fmt.Println("Заказов всего:", orders)
	fmt.Println("На одного курьера:", orders/couriers)
	fmt.Println("Готово")
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
Заказов всего: 120
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.main()
	/home/stagiaire/gocourse/day03/scratch/main.go:9 +0x65
exit status 2

Программа собралась: что лежит в переменной couriers, компилятор не отслеживает. Первая строка напечаталась, на девятой программа встала, «Готово» не появилось. Строка goroutine 1 [running]: говорит, в какой «нити» выполнения это случилось; горутины — в блоке 11.

Как это читать.

Строка Что значит
panic: runtime error: integer divide by zero что случилось: ошибка времени выполнения (runtime), целое деление на ноль
main.main() в какой функции: main из пакета main
…/scratch/main.go:9 +0x65 где: файл и номер строки; +0x65 — место в машинных командах, его пропускаем
exit status 2 это пишет уже go run: программа завершилась с кодом 2, то есть аварийно

Порядок чтения паники: первая строка — что, строка с main.go:N — где. Дальше как с ошибкой компилятора: открыть строку и понять, откуда там ноль. Собранный файл, запущенный как ./scratch, напечатает то же, но без exit status 2 — это слова go run.

Остаток от деления на ноль — та же паника. А дробные числа не паникуют: у float64 есть «бесконечность», и weight / boxes при boxes := 0.0 печатает +Inf. Программа не упадёт, но +Inf пойдёт дальше в расчёты — ещё одна причина не считать деньги в float64. Проверять делитель — в день 06, когда появится if.


Попробуйте сейчас: где упадёт?

Цель: найти без запуска строку, на которой программа остановится с паникой, и отправить её номер.

Код для чтения · разбираем, набирать не нужно
package main

import "fmt"

func main() {
	stock := 480
	perPallet := 60
	pallets := stock / perPallet
	trucks := pallets / 10
	fmt.Println("Паллет:", pallets)
	fmt.Println("Машин:", trucks)
	fmt.Println("Паллет на машину:", pallets/trucks)
}

Строки считаются от начала файла: package main — строка 1. Ответьте номером строки, который будет в сообщении паники после main.go:.

▶ Выполните · выделенное замените своим
course answer day03.q5 ЧИСЛО

Потом проверьте себя запуском — программу нужно набрать в scratch/main.go:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	stock := 480
	perPallet := 60
	pallets := stock / perPallet
	trucks := pallets / 10
	fmt.Println("Паллет:", pallets)
	fmt.Println("Машин:", trucks)
	fmt.Println("Паллет на машину:", pallets/trucks)
}
▶ Выполните
cd ~/gocourse/day03/scratch
go run .

Найдите в выводе все части из таблицы выше.

Готово, когда: ответ отправлен — пункт проверки q5.


Переполнение

У int есть предел: на 64-битном компьютере от −9223372036854775808 до 9223372036854775807. Число в коде больше предела не пропустит компилятор:

Код для чтения · разбираем, набирать не нужно
	var n int = 9223372036854775808
	fmt.Println(n)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:6:14: cannot use 9223372036854775808 (untyped int constant) as int value in variable declaration (overflows)

А если предел превышен во время работы, паники не будет: n := 9223372036854775807 и n = n + 1 печатают -9223372036854775808. Самое большое число плюс один молча стало самым маленьким — как счётчик пробега, перевалив за девятки. Для копеек предел — 92 квадриллиона рублей, складу хватит, но при умножении больших чисел о нём стоит помнить.


Что может пойти не так

Что видите Что это значит Что делать
invalid operation: division by zero делитель — ноль из кода или константы исправить делитель
panic: runtime error: integer divide by zero делитель-переменная во время работы оказался нулём строка main.go:N — где; понять, откуда ноль
+Inf или -Inf в выводе дробное деление на ноль найти, где делитель стал нулём
… as int value in variable declaration (overflows) число не помещается в int проверить число: лишние цифры?
огромное отрицательное число вместо положительного переполнение во время работы искать умножение больших чисел

Словарик

К концу дня в ~/errors.md должно быть не меньше семи разборов — вместе с записями прошлых дней. Кандидаты — таблицы «Что может пойти не так» со всех страниц: truncated, mismatched types, cannot convert, сообщения go vet, %!d(MISSING), division by zero, паника. Формат прежний: текст ошибки, тире, что значило у вас. Разделитель — только тире (« — » или « - »): двоеточие есть в каждом сообщении компилятора.


Попробуйте сейчас: поймайте панику сами.

Цель: увидеть на одной программе панику, нормальный вывод и ошибку компиляции, а словарик дописать до семи разборов.

  1. В scratch/main.go наберите программу про курьеров из раздела «Паника»:
✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	orders := 120
	couriers := 0
	fmt.Println("Заказов всего:", orders)
	fmt.Println("На одного курьера:", orders/couriers)
	fmt.Println("Готово")
}
  1. Запустите:
▶ Выполните
cd ~/gocourse/day03/scratch
go run .
  1. Найдите в выводе строку с номером: совпадает ли он со строкой деления в VS Code?
  2. Поменяйте couriers := 0 на couriers := 8 — паника исчезнет, «Готово» напечатается.
  3. Верните ноль и замените orders/couriers на orders/0 — ошибка станет ошибкой компиляции, сообщений будет два:
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:7:2: declared and not used: couriers
./main.go:9:58: invalid operation: division by zero

Первое — побочное: в строке 9 больше нет couriers, и переменная из строки 7 нигде не читается (это вы видели в дне 02). Колонка 58 больше длины строки: русские буквы считаются по два байта, как в дне 01.

  1. Допишите словарик до семи разборов — каждый через тире.

Готово, когда: вы увидели панику, вывод с «Готово» и два сообщения компилятора, а в ~/errors.md не меньше семи разборов — пункт errors.


Материал дня закончен. Задания — course lab 03, дополнительное чтение — course extra 03