惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
The Register - Security
The Register - Security
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
罗磊的独立博客
U
Unit 42
S
SegmentFault 最新的问题
Y
Y Combinator Blog
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
J
Java Code Geeks
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
C
Check Point Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
V
Vulnerabilities – Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
S
Securelist
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Scott Helme
Scott Helme
博客园 - 聂微东
博客园 - 【当耐特】
T
Tenable Blog
I
Intezer
D
DataBreaches.Net
B
Blog RSS Feed
Security Latest
Security Latest
C
Cisco Blogs
T
Tor Project blog
N
Netflix TechBlog - Medium

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Float в Go: что должен понимать разработчик, чтобы не ловить странные баги
che1nov · 2026-05-05 · via Все публикации подряд на Хабре

С типом float рано или поздно сталкивается почти любой разработчик. Сначала все выглядит просто. Есть float32, есть float64, можно хранить дробные числа, делить, умножать, считать проценты, средние значения, коэффициенты и что угодно еще. Кажется, что это просто «числа с точкой».

Но именно здесь у многих начинаются странные баги.

Почему 0.1 + 0.2 != 0.3? Почему после серии вычислений число внезапно становится 9.99999999997 вместо 10? Почему сравнение двух значений с float64 иногда работает, а иногда ломает логику? Почему в деньгах float почти всегда плохая идея? И почему даже корректная формула может давать нестабильный результат?

Проблема не в Go. Проблема в том, что float устроен не так, как его часто себе представляют. Это не «точное дробное число», а приближенное представление вещественных чисел в памяти компьютера.

В этой статье разберем, как устроен float в Go, где он полезен, где опасен, какие ошибки встречаются чаще всего и какие практики помогают не ловить неприятные сюрпризы в проде.

Почему float вообще существует

Если бы компьютеры умели хранить любые вещественные числа точно и без ограничений, отдельной темы бы не было. Но память конечна, а множество вещественных чисел бесконечно. Поэтому компьютер хранит не «настоящее число из математики», а его конечное приближение.

Для целых чисел это выглядит привычно. Например, int хранит целое значение в фиксированном числе битов. Для дробных значений нужен другой механизм, потому что числа могут быть очень маленькими, очень большими и содержать дробную часть.

Именно для этого используются числа с плавающей точкой, или floating point numbers.

Смысл в том, что число хранится примерно так:

sign × mantissa × base^exponent

В двоичной системе это позволяет представлять очень широкий диапазон значений. Именно поэтому float64 умеет хранить и довольно маленькие, и очень большие числа. Но за эту гибкость приходится платить точностью.

Какие float есть в Go

В Go есть два основных типа для чисел с плавающей точкой:

var a float32
var b float64

На практике почти всегда используется float64.

Потому что float64 дает заметно более высокую точность, а стоимость памяти в большинстве прикладных задач не настолько критична, чтобы ради нее массово переходить на float32.

Грубо говоря, float32 подходит там, где важнее экономия памяти или пропускная способность, например в графике, обработке больших массивов чисел, некоторых ML‑задачах или low‑level вычислениях. В обычной backend‑разработке, аналитике, сервисной логике и большинстве библиотечного кода почти всегда разумнее брать float64.

Почему 0.1 нельзя представить точно

Вот главная мысль, которую нужно усвоить раз и навсегда: многие десятичные дроби нельзя точно представить в двоичном виде.

Точно так же, как дробь 1/3 в десятичной системе выглядит как бесконечная периодическая запись 0.333333..., число 0.1 в двоичной системе тоже превращается в бесконечную дробь. А компьютер не может хранить бесконечную запись. Он хранит только конечное приближение.

Из‑за этого число 0.1 в памяти уже не является идеальным математическим 0.1. Это очень близкое значение, но не точное.

Поэтому такой код:

package main

import "fmt"

func main() {
	fmt.Println(0.1 + 0.2)
}

может вывести не строго 0.3, а что‑то вроде:

0.30000000000000004

В Go формат вывода часто делает результат визуально аккуратнее, но проблема никуда не исчезает. Она просто не всегда бросается в глаза.

Демонстрация главной проблемы

Посмотрим на простой пример:

package main

import "fmt"

func main() {
	fmt.Println(0.1 + 0.2 == 0.3)
}

Многие ожидают true, но на практике получают false.

Почему? Потому что левая часть и правая часть представлены в памяти как разные приближенные значения. Они очень близки, но не обязаны совпадать бит в бит.

Это одна из самых частых ошибок при работе с float: ожидание точного равенства там, где возможна только приблизительная близость.

Как устроен float64

Не обязательно учить стандарт IEEE 754 наизусть, но понимать основу полезно.

float64 использует 64 бита. Из них один бит уходит на знак, часть на порядок, часть на мантиссу. Мантисса отвечает за значащие цифры, порядок отвечает за масштаб числа.

Из этого следуют три практических вывода.

Первый вывод состоит в том, что float64 хранит число не абсолютно точно, а с ограниченной точностью.

Второй вывод в том, что точность зависит от масштаба. Чем больше число по модулю, тем грубее становится шаг между соседними представимыми значениями.

Третий вывод в том, что далеко не каждая арифметическая операция сохраняет ожидаемую человеческую точность.

Разработчику не обязательно помнить количество битов в каждом поле каждый день, но важно понимать сам принцип: float64 это компромисс между диапазоном и точностью.

Почему сравнение float через ==опасно

Сравнение через == для float не всегда ошибка, но почти всегда повод подумать еще раз.

Вот классический антипример:

func isEqual(a, b float64) bool {
	return a == b
}

Если значения получены в результате вычислений, такое сравнение может ломаться даже тогда, когда числа по смыслу одинаковы.

Гораздо безопаснее сравнивать числа с допуском:

package main

import (
	"fmt"
	"math"
)

func almostEqual(a, b, eps float64) bool {
	return math.Abs(a-b) < eps
}

func main() {
	fmt.Println(almostEqual(0.1+0.2, 0.3, 1e-9))
}

Здесь мы не требуем точного побитового совпадения. Мы проверяем, что разница между числами достаточно мала.

Это гораздо ближе к тому, как с float нужно работать в реальной жизни.

Что такое epsilon и почему он важен

epsilon или eps это небольшой допуск, с которым мы считаем числа практически равными.

Идея простая. Если два значения отличаются на крошечную величину, которая несущественна для нашей задачи, мы считаем их равными.

Но здесь есть тонкость. Нельзя выбрать один «магический» eps, который подойдет для всех задач.

Например, допуск 1e-9 может быть нормальным для геометрии в одних масштабах и совершенно неподходящим для финансовых расчетов или чисел порядка 1e12.

Поэтому хороший инженерский подход такой: допуск нужно выбирать в контексте предметной области.

Если вы работаете с расстояниями в метрах, это один масштаб. Если с процентами, другой. Если с научными вычислениями, третий.

Абсолютная и относительная погрешность

Когда мы сравниваем числа с float, полезно различать два подхода.

Абсолютная погрешность проверяет, что разница между числами по модулю меньше фиксированного порога:

math.Abs(a-b) < eps

Это хорошо работает, когда значения находятся примерно в одном масштабе и сам масштаб невелик.

Но если числа очень большие, фиксированный eps может быть слишком грубым или слишком строгим. Тогда полезнее относительная погрешность, когда мы сравниваем ошибку относительно величины самих чисел.

Пример более устойчивой проверки:

package main

import (
	"math"
)

func almostEqualRelative(a, b, eps float64) bool {
	diff := math.Abs(a - b)
	scale := math.Max(math.Abs(a), math.Abs(b))

	if scale == 0 {
		return diff < eps
	}

	return diff/scale < eps
}

Такой подход лучше учитывает масштаб значений.

На практике иногда используют смешанную стратегию: учитывать и абсолютную, и относительную погрешность.

Почему порядок операций может менять результат

В математике мы привыкли, что если выражение эквивалентно, то результат тот же. В вычислениях с float это не всегда так.

Посмотрим на пример:

package main

import "fmt"

func main() {
	a := 1e16
	b := 1.0
	c := -1e16

	fmt.Println((a + b) + c)
	fmt.Println(a + (b + c))
}

Результаты могут отличаться.

Почему? Потому что промежуточные значения округляются. Когда к очень большому числу прибавляется маленькое, маленькое может просто «потеряться» из‑за ограниченной точности. После этого дальнейшие вычисления уже идут с искаженным промежуточным результатом.

Это важная мысль: вычисления с float не всегда ассоциативны так, как в обычной математике.

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

Ошибка накопления при суммировании

Одна из самых частых практических проблем с float связана с накоплением ошибки.

Например, вы суммируете большое количество дробных значений:

package main

import "fmt"

func main() {
	var sum float64
	for i := 0; i < 1000000; i++ {
		sum += 0.1
	}
	fmt.Println(sum)
}

Интуитивно ожидается 100000.0, но реальный результат может немного отличаться.

Причина в том, что ошибка представления 0.1 повторяется снова и снова, а затем накапливается.

Если задача чувствительна к точности, такое суммирование стоит делать осторожнее.

Более устойчивое суммирование

Для некоторых задач полезно использовать более устойчивые алгоритмы суммирования, например алгоритм Кэхэна.

Пример:

package main

import "fmt"

func kahanSum(nums []float64) float64 {
	var sum float64
	var c float64

	for _, x := range nums {
		y := x - c
		t := sum + y
		c = (t - sum) - y
		sum = t
	}

	return sum
}

func main() {
	nums := make([]float64, 1000000)
	for i := range nums {
		nums[i] = 0.1
	}

	fmt.Println(kahanSum(nums))
}

Не во всех прикладных сервисах нужен именно такой уровень аккуратности, но важно знать, что «просто складывать в цикле» не всегда лучший вариант, если точность критична.

Почему float плохо подходит для денег

Это один из самых важных практических пунктов.

Деньги нельзя безопасно хранить в float, если вам нужна точность на уровне копеек, центов и бухгалтерской логики.

Почему? Потому что float хранит число приближенно. А в финансовой логике ошибка на одну копейку уже может быть проблемой. Особенно если операции массовые и ошибки накапливаются.

Плохой вариант:

type Account struct {
	Balance float64
}

Такой код может работать на игрушечных примерах, но в реальной финансовой логике быстро становится источником проблем.

Надежнее хранить деньги в минимальных целых единицах. Например, не рубли, а копейки:

type Account struct {
	BalanceKopecks int64
}

Тогда 1050 означает 10 рублей 50 копеек. Все операции становятся точными на уровне целых чисел.

Если предметная область сложнее и нужна высокая точность для десятичных расчетов, используют decimal‑библиотеки, а не float64.

Когда float использовать нормально

После всех предупреждений может показаться, что float это почти всегда зло. Это не так.

float64 абсолютно нормален там, где допустима небольшая погрешность и где модель данных сама по себе является непрерывной или приближенной.

Например, float подходит для:

расчетов в графике и геометрии, научных и инженерных вычислений, статистики, ML, работы с коэффициентами, вероятностями, средними значениями, физическими моделями, метриками, если небольшая погрешность допустима.

Если вы работаете с температурой, координатами, весами, скоростями, вероятностями или аналитическими коэффициентами, float64 обычно вполне уместен.

Ключевой вопрос всегда один: допустима ли в вашей задаче небольшая погрешность?

Если да, float64 нормален. Если нет, нужно искать другой тип данных.

float32 или float64

На практике ответ в большинстве случаев простой: берите float64, если у вас нет очень понятной причины брать float32.

Почему float64 обычно лучше:

у него выше точность, он безопаснее для большинства вычислений, он чаще используется в стандартных библиотеках и научных пакетах, он снижает риск неожиданных ошибок из‑за потери точности.

Когда может быть нужен float32:

когда важна экономия памяти, когда работаете с большими массивами чисел, когда данные приходят именно в таком формате, когда задача и так допускает заметную погрешность, когда есть требования от конкретного API или GPU‑пайплайна.

Но если вы не уверены, float64 почти всегда лучший выбор по умолчанию.

Особые значения: NaN, +Inf, ‑Inf

Еще одна тема, которую нельзя игнорировать при работе с float, это специальные значения.

В Go, как и в IEEE 754, существуют:

  • NaN — not a number

  • +Inf — положительная бесконечность

  • -Inf — отрицательная бесконечность

Они могут появляться в вычислениях, например:

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(0.0 / 0.0)
	fmt.Println(1.0 / 0.0)
	fmt.Println(-1.0 / 0.0)

	fmt.Println(math.NaN())
	fmt.Println(math.Inf(1))
	fmt.Println(math.Inf(-1))
}

Практически это важно потому, что такие значения могут незаметно «протечь» через систему и испортить дальнейшие вычисления.

Особенно коварен NaN. У него есть необычное свойство: NaN != NaN.

То есть такой код:

x := math.NaN()
fmt.Println(x == x)

выведет false.

Поэтому для проверки нужно использовать math.IsNaN(x) и math.IsInf(x, sign).

Как NaN ломает логику

Представим, что у вас есть аналитический пайплайн, который вычисляет среднее значение. Где‑то в промежуточной формуле происходит деление на ноль, возникает NaN, а затем это значение идет дальше по системе.

На выходе вы можете получить:

  • сломанный график,

  • пустую метрику,

  • странную сортировку,

  • некорректный JSON,

  • падение на валидации,

  • странный ответ API.

Проблема в том, что NaN часто не вызывает немедленного падения. Он может жить внутри вычислений довольно долго.

Поэтому в критичных местах полезно явно проверять результаты:

func safeValue(x float64) error {
	if math.IsNaN(x) {
		return fmt.Errorf("value is NaN")
	}
	if math.IsInf(x, 0) {
		return fmt.Errorf("value is infinite")
	}
	return nil
}

Это особенно важно в аналитике, ML, численных расчетах и любых сервисах, где результат вычислений идет дальше в бизнес‑логику.

Форматирование float при выводе

Иногда проблема не в самих вычислениях, а в том, как число показывается пользователю.

Например:

price := 12.3456789
fmt.Println(price)
fmt.Printf("%.2f\n", price)

Первый вариант может показать длинную дробную часть, второй округлит до двух знаков после запятой.

Важно понимать, что форматирование не делает число «точнее». Оно только меняет способ отображения.

То есть fmt.Printf("%.2f", x) не лечит проблему точности, а просто красиво печатает результат.

Для UI и отчетов это нормально. Для логики вычислений нет.

Округление тоже не всегда тривиально

Многие думают, что округление с float это простая операция. На самом деле и тут бывают тонкости.

Пример:

package main

import (
	"fmt"
	"math"
)

func main() {
	x := 2.675
	fmt.Printf("%.2f\n", x)
	fmt.Println(math.Round(x*100) / 100)
}

Ожидания человека и реальный результат могут не совпасть, потому что исходное значение уже хранится как приближение.

То есть даже округление работает не с «идеальным 2.675», а с ближайшим представимым двоичным числом.

Если в системе важна десятичная точность по правилам бизнеса, одних float64 и Round может быть недостаточно.

Преобразования между int и float

Еще одна зона ошибок это преобразования между целыми и вещественными числами.

Например:

a := 5
b := 2
fmt.Println(float64(a) / float64(b))

Если забыть приведение типов и делить как int, получится 2, а не 2.5.

С другой стороны, обратное преобразование тоже требует осторожности:

x := 3.9
fmt.Println(int(x))

Результат будет 3, потому что преобразование к int просто отбрасывает дробную часть, а не округляет.

Это особенно важно в расчетах индексов, количестве страниц, процентных вычислениях и логике пагинации.

Когда float опасен в API и БД

Если сервис отдает или принимает значения с float, важно помнить, что разные системы могут сериализовать и десериализовать их чуть по‑разному.

Например, JSON нормально поддерживает числа, но дальше начинаются практические вопросы:

  • сколько знаков после запятой мы отдаем;

  • должен ли клиент видеть округленное значение;

  • нельзя ли потерять смысловую точность;

  • как фронтенд обработает число;

  • не попадет ли NaN или Inf в сериализацию.

Кстати, NaN и бесконечности в обычный JSON не укладываются как нормальные числовые значения. Это тоже нужно учитывать.

Если число идет наружу через API, полезно заранее решить, как именно вы его представляете: как float, как строку, как decimal, как целое число в минимальных единицах.

Пример хорошего практического подхода

Представим, что вы пишете сервис с аналитическими коэффициентами. Значения не финансовые, небольшая погрешность допустима, но вам важны предсказуемость и защита от мусора.

Тогда хорошая практика может выглядеть так:

  • внутри использовать float64,

  • после вычислений проверять NaN и Inf,

  • сравнивать значения через допуск,

  • на границе API форматировать результат осознанно,

  • не использовать ==, если число получено вычислением,

  • документировать допустимую погрешность.

Это уже выглядит как зрелый инженерный подход, а не как «ну это же просто число с точкой».

Полезные правила, которые стоит запомнить

Есть несколько правил, которые реально экономят время и нервы.

Первое правило: по умолчанию берите float64, а не float32.

Второе: не сравнивайте вычисленные float через ==, если вам нужна логическая близость, а не побитовое совпадение.

Третье: не храните деньги в float.

Четвертое: помните, что ошибка может накапливаться при большом числе операций.

Пятое: проверяйте NaN и Inf в важных вычислительных участках.

Шестое: не путайте красивый вывод числа и точность самого числа.

Седьмое: при выборе допуска думайте о масштабе задачи, а не используйте случайное 1e-9 везде подряд.

Что должен понимать Go‑разработчик про float

Если свести все к самому важному, то Go‑разработчику нужно понимать следующее.

float64 это приближенное представление вещественного числа, а не точное десятичное значение. Поэтому некоторые дроби нельзя представить без ошибки. Из‑за этого вычисления могут давать неожиданные на первый взгляд результаты, а сравнение через == часто оказывается ненадежным.

float64 отлично подходит для огромного числа инженерных задач, если предметная область допускает небольшую погрешность. Но для денег, бухгалтерии и других задач, где важна строгая десятичная точность, лучше использовать целые минимальные единицы или decimal‑подход.

Кроме того, разработчик должен помнить про NaN, бесконечности, накопление ошибки и влияние порядка операций на результат.

Этого уже достаточно, чтобы перестать воспринимать float как обычное число с точкой и начать использовать его осознанно.

Итог

float в Go это полезный инструмент, но только если понимать его ограничения. Большинство странных багов с ним возникают не потому, что тип плохой, а потому, что разработчик ожидает от него математической точности, которой у него нет по определению.

Если вы понимаете, что float64 хранит приближение, что сравнивать такие числа нужно осторожно, что деньги в нем хранить не стоит, а ошибки округления могут накапливаться, вы уже находитесь сильно выше среднего уровня практического понимания темы. И этого достаточно, чтобы многие баги перестали быть магией.