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

Дробные числа и преобразования

Целых чисел не хватает, когда нужна средняя цена, вес в килограммах или доля: для этого есть float64. Главное на странице: целые и дробные числа в Go не смешиваются сами, их переводят друг в друга явно.


Дробное деление

Ситуация. Семь килограммов разложить на два пакета поровну: целое деление даст 3, килограмм потерялся.

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

import "fmt"

func main() {
	fmt.Println(7/2, 7.0/2, 7/2.0)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
3 3.5 3.5

Как это читать. 7/2 — два целых, деление целое. В 7.0/2 и 7/2.0 одно число записано с точкой, оно дробное — и деление дробное. Числа из кода Go подгоняет друг под друга сам: целое 2 рядом с дробным 7.0 становится дробным без потерь. С переменными так не получится.


int и float64 не смешиваются

Код для чтения · разбираем, набирать не нужно
	a := 7
	b := 2.5
	fmt.Println(a + b)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:8:14: invalid operation: a + b (mismatched types int and float64)

Как это читать. invalid operation: a + b — «действие недопустимо», дальше причина: mismatched types int and float64 — типы не совпадают. Go не решает за вас, каким быть результату: дробным (9.5) или целым (9), — это говорят явно.


Преобразование типа

Запись float64(x) даёт новое значение типа float64, а сама переменная x остаётся int. Так же int(f) даёт целое из дробного.

Ситуация. Сумма 10, позиций 3, нужна средняя.

Код для чтения · разбираем, набирать не нужно
	sum := 10
	count := 3
	avg := float64(sum) / float64(count)
	fmt.Println(avg)
Вывод · это печатает программа, набирать не нужно
3.3333333333333335

Оба значения переведены в float64 до деления — деление дробное. Пятёрка в конце не опечатка: дробные числа хранятся неточно, подробно на пятой странице.

Преобразовать после деления — поздно:

Код для чтения · разбираем, набирать не нужно
	avg := float64(sum / count)
	fmt.Println(avg)
Вывод · это печатает программа, набирать не нужно
3

Сначала выполнилось sum / count — деление двух int, вышло 3, потом 3 превратилось в 3.0: потерянная дробь не вернулась. А запись float64(sum) / count не соберётся вовсе — float64 и int снова рядом.


Попробуйте сейчас: что выведет?

Цель: предсказать вывод с преобразованием до и после деления и отправить ответ.

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

import "fmt"

func main() {
	boxes := 9
	fmt.Println(float64(boxes)/2, float64(boxes/2))
}
▶ Выполните · выделенное замените своим
course answer day03.q3 ВАШ_ОТВЕТ

Вместо ВАШ_ОТВЕТ — строка вывода целиком.

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

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

import "fmt"

func main() {
	boxes := 9
	fmt.Println(float64(boxes)/2, float64(boxes/2))
}
▶ Выполните
cd ~/gocourse/day03/scratch
go run .

Разошлось — отправьте ответ заново, засчитывается последний.

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


int(f) отбрасывает дробь

Код для чтения · разбираем, набирать не нужно
	w := 3.99
	n := -3.99
	fmt.Println(int(w), int(n))
Вывод · это печатает программа, набирать не нужно
3 -3

int от дробного отбрасывает дробную часть — к нулю, как целое деление, а не округляет: 3.99 стало 3, а не 4. Округление — в день 04.


Константа или переменная

С переменной int(w) работает. С тем же числом, записанным прямо в коде, — нет:

Код для чтения · разбираем, набирать не нужно
	fmt.Println(int(3.99))
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:6:18: cannot convert 3.99 (untyped float constant) to type int

При этом int(4.0) компилируется и даёт 4.

Почему так. Число в коде — константа, её значение компилятор знает заранее: 4.0 станет целым без потерь, 3.99 — с потерей, и это ошибка сборки. Значение переменной известно только при работе, поэтому int(w) — явное разрешение отбросить дробь (подробнее — в course extra 03). Та же логика даёт ловушку: число без типа подгоняется под тип соседа.

Код для чтения · разбираем, набирать не нужно
	x := 7
	fmt.Println(x * 2.5)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day03/scratch$ go run .
# day03/scratch
./main.go:7:18: 2.5 (untyped float constant) truncated to int

x — целое, и 2.5 пытается стать целым: с потерей, ошибка. А 2.0 станет целым без потерь, и fmt.Println(x*2.0, x/2.0) печатает 14 3: x/2.0 выглядит дробным делением, но 2.0 превратилось в целое 2. Точка у числа не делает деление дробным, если рядом переменная int; надёжно — float64(x) / 2.


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

Что видите Что это значит Что делать
invalid operation: a + b (mismatched types int and float64) действие между int и float64 перевести одно значение: float64(a) + b или a + int(b)
cannot convert 3.99 (untyped float constant) to type int константа с дробью, дробь потерялась бы целое число или переменная float64
2.5 (untyped float constant) truncated to int дробное число рядом с переменной int float64(x) * 2.5
float64(sum / count) печатает целое деление выполнилось до преобразования переводить до деления: float64(sum) / float64(count)
средняя на копейку меньше делили в int, float64 взяли после деления переводить до деления; о средней — пятая страница

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

Цель: fix1 собирается и печатает то, что обещано в первых строках файла, а правка — точечная.

  1. Программа в fix1 не собирается — запустите её:
▶ Выполните
cd ~/gocourse/day03/fix1
go run .
  1. В первых строках файла — что она должна напечатать. Прочитайте сообщение: какие два типа не сошлись? Почините так, чтобы программа печатала обещанное. Хватает одной строки, принимается не больше двух: переписанную заново программу проверка не засчитает, а копию запустит с другим числом коробок — вписать готовый вес не выйдет.
  2. Сообщение компилятора — в ~/errors.md: текст ошибки, тире, что значило.

Готово, когда: go run . печатает обещанную в начале файла строку и изменено не больше двух строк — пункт fix1; в ~/errors.md новая строка с тире.


Дальше: строки и форматированный вывод — course next