День 04 · Чужой код и готовые функции · страница 3 из 7

Числа: math и strconv

В дне 03 вы узнали, что 150 / 12 у целых чисел даёт 12, а дробь теряется. Но 150 кружек по 12 в коробке — это 13 коробок, иначе шесть кружек поедут без упаковки. Сегодня — функции, которые округляют как нужно и превращают число в строку.


Ситуация: коробки и цена

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

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	qty := 150
	perBox := 12
	full := math.Floor(float64(qty) / float64(perBox))
	boxes := math.Ceil(float64(qty) / float64(perBox))
	fmt.Println("Полных коробок:", full)
	fmt.Println("Коробок нужно:", boxes)
	fmt.Println("Цена:", math.Round(1299.5), "руб.")
	msg := "Заказ №" + strconv.Itoa(1042) + " собран"
	fmt.Println(msg)
}

Что увидите:

Вывод · это печатает программа, набирать не нужно
Полных коробок: 12
Коробок нужно: 13
Цена: 1300 руб.
Заказ №1042 собран

math: три способа округлить

Функция Что делает Где пригодится
math.Floor(x) вниз, до целого не больше x сколько коробок заполнится целиком
math.Ceil(x) вверх, до целого не меньше x сколько коробок нужно, чтобы влезло всё
math.Round(x) до ближайшего целого; половина — от нуля цена в рублях на ценнике

Результаты на Go 1.27:

x Floor Ceil Round
1299.49 — — 1299
1299.5 — — 1300
1299.99 1299 — —
1299.01 — 1300 —
-2.5 -3 — -3

Как читать программу выше. Все три функции принимают float64 и возвращают float64. Поэтому qty и perBox сначала превращаются в дробные: float64(qty) / float64(perBox) — это 12.5. Дальше Floor даёт 12, Ceil — 13.

Порядок здесь решает всё. Запись math.Ceil(float64(qty / perBox)) выглядит почти так же, но печатает 12: сначала выполняется целочисленное деление 150 / 12 = 12, дробь теряется, и округлять уже нечего. Превращайте в float64 до деления.

Println печатает float64 без лишней точки: 13, а не 13.0, — но тип от этого не меняется.

Деньги и float64. Запрет дня 03 в силе: цена хранится целым числом копеек. math.Round(1299.5) здесь не расчёт, а показ — округлить уже посчитанную сумму до рубля для ценника. Поэтому в задании label цена превращается в рубли записью math.Round(float64(priceKop) / 100): один float64 на последнем шаге, и обратно в расчёты он не возвращается.


strconv.Itoa — число в строку

Нужна строка Заказов: 17, а число лежит в orders типа int. Склеить напрямую нельзя:

Вывод · это печатает программа, набирать не нужно
./main.go:7:9: invalid operation: "Заказов: " + orders (mismatched types untyped string and int)

strconv.Itoa превращает целое число в строку, и склейка работает:

Код для чтения · разбираем, набирать не нужно
orders := 17
msg := "Заказов: " + strconv.Itoa(orders)
fmt.Println(msg)
Вывод · это печатает программа, набирать не нужно
Заказов: 17

Название — от Integer to ASCII. Обратную функцию, strconv.Atoi, разберём в день 07. Itoa принимает только int: дробное число ей не подойдёт. Встроенные min и max и fmt.Sprintf — в course extra 04.


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

Что видите Что это значит Что делать
./main.go:5:2: "math" imported and not used math подключён, а функции из него не вызываются убрать импорт или вызвать функцию
./main.go:19:14: undefined: strings вызвана функция strings, а пакета нет в import добавить "strings" — Ctrl+S сам его не добавит
cannot use boxes (variable of type float64) as int value in argument to strings.Repeat math.Ceil вернул float64, а Repeat ждёт int strings.Repeat("#", int(boxes))
invalid operation: "Заказов: " + orders (mismatched types untyped string and int) строку склеивают с числом strconv.Itoa(orders)
коробок на одну меньше, чем нужно float64 поставлен после целочисленного деления float64(qty) / float64(perBox)

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

Цель: увидеть Floor и Ceil на ровном делении, float64 после деления и float64 в аргументе Repeat.

Наберите в черновике программу из «Ситуации»:

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

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	qty := 150
	perBox := 12
	full := math.Floor(float64(qty) / float64(perBox))
	boxes := math.Ceil(float64(qty) / float64(perBox))
	fmt.Println("Полных коробок:", full)
	fmt.Println("Коробок нужно:", boxes)
	fmt.Println("Цена:", math.Round(1299.5), "руб.")
	msg := "Заказ №" + strconv.Itoa(1042) + " собран"
	fmt.Println(msg)
}
▶ Выполните
cd ~/gocourse/day04/scratch
go run .

Потом:

  1. Поменяйте qty на 144 — ровно 12 коробок. Совпали ли Floor и Ceil? Верните 150.
  2. Замените math.Ceil(float64(qty) / float64(perBox)) на math.Ceil(float64(qty / perBox)). Сколько коробок? Верните как было.
  3. Последней строкой main напечатайте решётки по числу коробок: fmt.Println(strings.Repeat("#", boxes)). Пакета strings в import нет, Ctrl+S его не добавит — будет undefined: strings. Добавьте "strings" сами, запустите, прочитайте новую ошибку и почините через int(...).

Готово, когда: на вопросы шагов 1 и 2 есть ответы по запуску, программа из шага 3 печатает строку из решёток, а новые сообщения записаны в ~/errors.md (пункт errors).


Попробуйте сейчас: что выведет, не запуская.

Цель: предсказать вывод по коду и отправить его.

Не запуская, предскажите вывод:

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

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(math.Ceil(25.0/12), math.Round(25.0/12), 25/12)
}

25.0/12 — дробное деление, 25/12 — целочисленное. Отправьте строку вывода целиком:

▶ Выполните · выделенное замените своим
course answer day04.q3 ВАШ_ОТВЕТ

Готово, когда: course answer ответил Принято: day04.q3 = … (пункт проверки q3).


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

Цель: label/main.go печатает строки с артикулом, ценой и числом коробок для вашего товара.

▶ Выполните
cd ~/gocourse/day04/label
cat TASK.txt

У каждого свой товар. Создайте main.go, объявите переменные из условия — каждую отдельной строкой, с тем же именем — и напечатайте строки с артикулом, ценой и числом коробок. Название и строку из = отложите до следующей страницы: там ловушка, из-за которой они выйдут неправильной ширины.

Готово, когда: go run . печатает строки с артикулом, ценой и числом коробок такими же, как в TASK.txt (пункты label и label_funcs засчитаются после следующей страницы, когда этикетка будет целиком).


Дальше: кириллица и len — course next