float64 и числа с плавающей точкой
float32 и float64
В Go два типа для дробных чисел:
var f32 float32 = 3.14 // 32 бита, ~7 значимых цифр
var f64 float64 = 3.14 // 64 бита, ~15 значимых цифрИспользуй float64 по умолчанию. float32 нужен только когда критична память (графика, машинное обучение) или при работе с C-библиотеками. В остальных случаях потеря точности float32 создаст больше проблем, чем выгода от экономии 4 байт.
Сокращённая запись (тип выводится как float64):
x := 3.14 // float64
y := 3.14e10 // float64 = 31400000000
z := .5 // float64 = 0.5IEEE 754: как хранится число
Каждый float64 — это 64 бита, разбитых на три поля:
[знак: 1 бит][экспонента: 11 бит][мантисса: 52 бита]
Значение вычисляется по формуле: (-1)^знак × 1.мантисса × 2^(экспонента−1023)
Интерактивный блок выше показывает битовое разложение для любого числа. Введи 0.1 и посмотри — ни одна из 52 бит не даёт точного представления. Число 0.1 в двоичной системе — бесконечная периодическая дробь, как 1/3 в десятичной.
Специальные значения
| Значение | Биты | Получается из |
|---|---|---|
+Inf | exp=11111111111, mantissa=0 | 1.0/0.0 |
-Inf | sign=1, exp=11111111111, mantissa=0 | -1.0/0.0 |
NaN | exp=11111111111, mantissa≠0 | 0.0/0.0, math.Sqrt(-1) |
0 | все биты = 0 | по умолчанию |
import "math"
x := 1.0 / 0.0 // +Inf
y := -1.0 / 0.0 // -Inf
z := 0.0 / 0.0 // NaN
w := math.Sqrt(-1) // NaN
math.IsInf(x, 1) // true (положительная бесконечность)
math.IsInf(y, -1) // true (отрицательная)
math.IsNaN(z) // true
// NaN не равно самому себе — единственный такой тип!
z == z // falseПроблема точности: 0.1 + 0.2 ≠ 0.3
Это не баг Go. Это фундаментальное ограничение IEEE 754:
a := 0.1 + 0.2
fmt.Println(a) // 0.30000000000000004
fmt.Println(a == 0.3) // falseЧисло 0.1 в двоичной: 0.0001100110011001100... (бесконечная дробь). При записи в 52 бита происходит округление. Сложение двух таких округлённых чисел накапливает ошибку.
Как сравнивать float правильно
import "math"
a, b := 0.1+0.2, 0.3
// НЕПРАВИЛЬНО:
a == b // false
// ПРАВИЛЬНО: сравнение с epsilon
const epsilon = 1e-9
math.Abs(a-b) < epsilon // true
// Для денег и финансов — используй целые числа (копейки):
price := 299 // 2.99 рублей в копейках
tax := 27 // 0.27 рублей
total := price + tax // 326 копеек = 3.26 рубляПравило: если деньги — никогда float. Используй int (центы/копейки) или пакет github.com/shopspring/decimal.
Конвертация типов
var i int = 42
var f float64 = float64(i) // 42.0
var back int = int(f) // 42
// int() всегда усекает к нулю (не округляет!):
int(3.9) // 3
int(-3.9) // -3
int(3.1) // 3
// Правильное округление:
math.Round(3.5) // 4.0
math.Floor(3.9) // 3.0 (вниз)
math.Ceil(3.1) // 4.0 (вверх)
math.Trunc(3.9) // 3.0 (к нулю)
// int после округления:
result := int(math.Round(3.9)) // 4Осторожно с большими числами:
var big float64 = 1e18
small := int(big) // OK на 64-битных платформах, int64 = 9.2e18
var tooBig float64 = 1e20
overflow := int(tooBig) // undefined behavior!Пакет math
Все стандартные математические функции принимают и возвращают float64:
import "math"
// Тригонометрия (аргументы в радианах)
math.Sin(math.Pi/2) // 1.0
math.Cos(0) // 1.0
math.Tan(math.Pi/4) // 1.0 (приблизительно)
// Степени и логарифмы
math.Sqrt(16) // 4.0
math.Pow(2, 10) // 1024.0
math.Log(math.E) // 1.0
math.Log2(1024) // 10.0
math.Log10(100) // 2.0
// Округление
math.Round(2.5) // 3.0 (банковское: к ближайшему чётному)
math.Round(3.5) // 4.0
// Абсолютное значение
math.Abs(-3.14) // 3.14
// Min/Max (Go 1.21+ также есть встроенные min/max для чисел)
math.Min(3.0, 5.0) // 3.0
math.Max(3.0, 5.0) // 5.0Константы
math.Pi // 3.141592653589793
math.E // 2.718281828459045
math.Phi // 1.618033988749895 (золотое сечение)
math.Sqrt2 // 1.4142135623730951
math.MaxFloat64 // 1.7976931348623157e+308
math.SmallestNonzeroFloat64 // 5e-324fmt и вывод float
f := 3.14159265358979
fmt.Println(f) // 3.14159265358979
fmt.Printf("%.2f\n", f) // 3.14 (2 знака после запятой)
fmt.Printf("%.0f\n", f) // 3 (без дробной части)
fmt.Printf("%e\n", f) // 3.141593e+00 (научная нотация)
fmt.Printf("%g\n", f) // 3.14159265358979 (компактный)
fmt.Printf("%9.3f\n", f)// ____3.142 (ширина поля 9, 3 знака)Для конвертации в строку:
import "strconv"
s := strconv.FormatFloat(3.14159, 'f', 2, 64) // "3.14"
s2 := strconv.FormatFloat(3.14159, 'g', -1, 64) // "3.14159" (минимум цифр)
f, err := strconv.ParseFloat("3.14", 64) // 3.14, nilКогда float64 недостаточен
Если нужна абсолютная точность (финансы, криптография, научные вычисления):
import "math/big"
// big.Float — произвольная точность
a := new(big.Float).SetString("0.1")
b := new(big.Float).SetString("0.2")
sum := new(big.Float).Add(a, b)
fmt.Println(sum.Text('f', 20))
// 0.30000000000000000000 ← точно!
// big.Rat — точные рациональные дроби
r1 := big.NewRat(1, 3) // 1/3
r2 := big.NewRat(2, 3) // 2/3
r3 := new(big.Rat).Add(r1, r2)
fmt.Println(r3) // 1/1math/big медленнее обычного float64 примерно в 10-100 раз — используй только там, где точность критична.
В следующем уроке разберём управляющие конструкции — if, switch, for и новшества Go 1.22.
0.1 + 0.2→0.30000000000000004ожидалось: 0.30.1 + 0.2 == 0.3→falseожидалось: true1.0 + 2.0→30.1 * 3→0.30000000000000004ожидалось: 0.3math.Abs(0.1+0.2-0.3) < 1e-9→true