Текст "Go"
и число 28487
на компьютере с архитектурой x86 представлены одним и тем же набором нулей и единиц — 0110111101000111
. Тип устанавливает, что данные биты и байты означают. В первом случае это строка string из двух символов, а во втором случае — 16-битное число integer (2 байта). Тип string используется для многоязычного текста, а 16-битный integer является одним из числовых типов.
Далее будут рассмотрено представление вещественных чисел в Golang. По окончании урока вы сможете:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
- Использовать два типа данных для вещественных чисел;
- Разобраться в компромиссе между памятью и точностью;
- Избавиться от ошибок округления ваших числовых данных.
Компьютерное хранилище манипулирует вещественными числами вроде 3.14159, используя IEEE-754 стандарт с плавающей запятой. Числа с плавающей запятой могут быть как очень крупными, так и чрезвычайно малыми — для сравнения подумайте о галактиках и атомах. С многогранностью подобного рода языки программирования вроде JavaScript и Lua справляются через исключительное использование чисел с плавающей запятой. Компьютеры также используют integer для целых чисел, о чем мы поговорим в следующем уроке.
Объявление переменных с плавающей запятой в Golang
У каждой переменной есть тип. При объявлении и инициализации переменной с вещественным числом используется тип с плавающей запятой float. Следующие три строки кода эквивалентны, так как компилятор Go отнесет переменную days
к типу float64
даже без дополнительного уточнения:
1 2 3 |
days := 365.2425 // краткое объявление var days = 365.2425 var days float64 = 365.2425 |
Важно знать, что у переменной days
тип float64
, излишне уточнять float64
. Разработчики и компилятор Go и так поймут тип, просто посмотрев на значение справа. Если рассматривается число с десятичной точкой, его тип всегда будет float64
.
При написании кода специальный инструмент golint
выдает подсказки для правки. Беспорядочный код будет прокомментирован следующим сообщением:
1 2 |
"should omit type float64 from declaration of var days; it will be inferred from the right-hand side" |
При инициализации переменной с целым числом Go не будет знать, что вам требуется тип с плавающей запятой, пока вы не уточните данный тип с плавающей запятой:
1 |
var answer float64 = 42 |
Вопрос для проверки:
Какого типа будет переменная answer := 42.0
?
Числа одинарной точности float32
В Go есть два типа данных для чисел с плавающей запятой. По умолчанию присваивается float64
, 64-битный тип с плавающей запятой, что использует восемь байтов памяти. В некоторых языках программирования при описании 64-битного типа с плавающей запятой используется термин двойная точность.
Тип float32
задействует половину используемой float64
памяти, но является менее точным. Данный типа еще называют одинарной точностью. Для использования float32
во время объявления переменной нужно уточнить ее тип. В следующем коде показан пример использования float32
:
1 2 3 4 5 |
var pi64 = math.Pi var pi32 float32 = math.Pi fmt.Println(pi64) // Выводит: 3.141592653589793 fmt.Println(pi32) // Выводит: 3.1415927 |
При работе с большим объемом данных вроде тысяч вершин в 3D-игре будет разумно пожертвовать точностью ради экономии памяти с помощью float32
.
На заметку: Функции из пакета math
оперируют с типами float64
. Если у вас нет четкой причины выбрать иной тип, отдавайте предпочтение float64
.
Вопрос для проверки:
Сколько байт памяти уходит на использование одинарной точности float32
?
Нулевое значение в Golang
В Go у каждого типа есть значение по умолчанию, которое называется нулевым значением. Значение по умолчанию присваивается при объявлении переменной, которая не инициализируется конкретным значением. Код с примером представлен далее:
1 2 |
var price float64 fmt.Println(price) // Выводит: 0 |
В данном коде объявляется переменная price
без значения, поэтому Go инициализирует ее с нулем. Для компьютера код идентичен следующему:
1 |
price := 0.0 |
Для программиста разница едва уловима. Объявление price := 0.0
равносильно тому, что товар бесплатен. Отсутствие уточнения значения price
является своеобразной подсказкой, что значение будет добавлено в будущем.
Вопрос для проверки:
Что означает нулевое значение для float32
?
Отображение типа чисел с плавающей запятой в Golang
При использовании Print
и Println
для типов с плавающей запятой по умолчанию выводится столько знаков, сколько возможно. Если вам это не нужно, используйте Printf
с символом для форматирования %f
для уточнения количества чисел после запятой. Код с примером дан ниже:
1 2 3 4 5 6 |
third := 1.0 / 3 fmt.Println(third) // Выводит: 0.3333333333333333 fmt.Printf("%v\n", third) // Выводит: 0.3333333333333333 fmt.Printf("%f\n", third) // Выводит: 0.333333 fmt.Printf("%.3f\n", third) // Выводит: 0.333 fmt.Printf("%4.2f\n", third) // Выводит: 0.33 |
Специальный символ %f
форматирует значение third
с шириной (width) и точностью (precision). Схема представлена ниже:
Специальный символ %f
для форматирования
Точность, или precision уточняет, сколько знаков вещественного числа должно выводиться после точки. В следующем примере выводится два знака — %.2f
:
Результат, отформатированный с шириной (width) 4 и точностью (precision) 2
Ширина уточняет минимальное число выводимых символов, включая точку вместе с числами до и после нее. К примеру, ширина числа 0.33 равна четырем. Если ширина больше количества необходимых символов, Printf
заполнит оставшееся место пробелами. Если ширина не уточняется, Printf
использует количество символов, необходимое для отображения значения.
Для заполнения пропуском нулями вместо пробелов требуется добавить в префикс ширины ноль. Пример дан ниже:
1 |
fmt.Printf("%05.2f\n", third) // Выводит: 00.33 |
Задания для проверки:
- Самостоятельно наберите код из
листинга 3
, один из примеров урока, в тело функцииmain
на Go Playground. Попробуйте различные значения для ширины и точности в оператореPrintf
. - Каковы значения ширины и точности 0015.1021?
Точность чисел с плавающей запятой в Go
В математике некоторые рациональные числа не могут быть точно представлены в форме десятичной дроби. Число 0.33 является лишь приближенным значением дроби 1/3. Неудивительно, что при проведении операций над приближенными значения результат также является приближенным:
1/3 + 1/3 + 1/3 = 1
0.33 + 0.33 + 0.33 = 0.99
Числа с плавающей запятой также страдают от ошибок округления. Разница в том, что машины используют бинарное представление (нули и единицы) вместо десятичного (1-9). В результате компьютеры могут точно передать значение 1/3, но с другими числами могут быть вызваны ошибки округления. Это показано в следующем примере:
1 2 3 4 5 6 |
third := 1.0 / 3.0 fmt.Println(third + third + third) // Выводит: 1 piggyBank := 0.1 piggyBank += 0.2 fmt.Println(piggyBank) // Выводит: 0.30000000000000004 |
Как видно в примере, числа с плавающей запятой — это не самый лучший выбор для подсчета денег. В качестве альтернативы значение суммы можно хранить в центах, что будут представлены типом целых чисел integer
. Данный тип будет рассмотрен в следующем уроке.
С другой стороны, хотя piggyBank
потерял цент, это не критично для крупных предприятий или покупок. Спрятать ошибки округления можно через использование Printf
с точностью в два знака.
Для уменьшения ошибок округления рекомендуется проводить умножение перед делением. Как правило, при осуществлении вычислений в таком порядке результат более точный. Это показано в примерах ниже на примере конвертера температуры:
1 2 3 4 |
celsius := 21.0 fmt.Print((celsius/5.0*9.0)+32, "° F\n") fmt.Print((9.0/5.0*celsius)+32, "° F\n") // В выводе: 69.80000000000001° F |
1 2 3 |
celsius := 21.0 fahrenheit := (celsius * 9.0 / 5.0) + 32.0 fmt.Print(fahrenheit, "° F") // Выводит: 69.8° F |
Вопрос для проверки:
Лучший способ избежать ошибок округления?
Сравнение чисел с плавающей запятой
В примере из листинга 5 значение piggyBank
0.30000000000000004, а не описанное 0.30. Имейте это в виду, когда решите сравнить числа с плавающей запятой:
1 2 3 |
piggyBank := 0.1 piggyBank += 0.2 fmt.Println(piggyBank == 0.3) // Выводит: false |
Вместо прямого сравнения чисел с плавающей запятой определите абсолютную разницу между двумя числами, а затем убедитесь, что разница не слишком велика. Для принятия абсолютного значения float64
в пакете math
есть функция Abs
:
1 |
fmt.Println(math.Abs(piggyBank-0.3) < 0.0001) // Выводит: false |
На заметку: Верхняя граница для ошибки с плавающей запятой для одной операции известна как машинный ноль. Его значение равно 2-52 для
float64
и 2-23 дляfloat32
. К сожалению, ошибки чисел с плавающей запятой можно получить очень быстро. Добавьте 11 монет ($0.10 каждая) кpiggyBank
, и ошибки округления превысят 2-52 по сравнению с $1.10. Это значит, что лучше выбирать допустимое отклонение, отталкиваясь от особенностей рассматриваемого приложения — в данном случае это 0.0001.
Вопрос для проверки:
При добавлении 11 монет ($0.10 каждая) к пустой переменной piggyBank
типа float64
каким станет финальный результат?
Заключение
- Go может назначать тип автоматически. В частности, вещественные числа инициализируются переменными типа
float64
; - Числа с плавающей запятой многогранны, но не всегда точны;
- Мы рассмотрели 2 из 15 числовых типов данных Go:
float64
иfloat32
.
Итоговое задание для проверки:
Представьте, что вам нужно накопить денег на подарок другу. Напишите программу, которая случайным образом размещает монеты пять ($0.05), десять ($0.10) и двадцать пять ($0.25) центов в пустую копилку до тех пор, пока внутри не будет хотя бы двадцать долларов ($20.00). Пускай после каждого пополнения копилки текущий баланс отображается на экране, отформатированный с нужной шириной и точностью.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
fmt.Println(math.Abs(piggyBank-0.3) < 0.0001) // Выводит: false
Ошибка в примере.
Выводит true, потому что 0.00000000000000004<0.0001
А я так сделал .
package main
import (
«fmt»
«math/rand»
)
func main() {
numbers := []int{5, 10, 25}
kopilka := 0
for kopilka < 2000 {
index := rand.Intn(len(numbers))
moneta := numbers[index]
kopilka = kopilka + moneta
fmt.Println(«Добавлено», moneta, «Центов. Текущая сумма:», kopilka/100, «долларов»)
}
}
У меня так получилось:
package main
import (
«fmt»
«math/rand»
)
func main() {
var kopilka float64
var add float64
for {
count := rand.Intn(3)
switch count {
case 0:
add = 0.05
case 1:
add = 0.10
case 2:
add = 0.25
}
kopilka += add
fmt.Printf(«%4.2f\n«, kopilka)
if kopilka >= 20.00 {
break
}
}
}
Получилось вроде