Преобразование float64 в int в Go: как правильно отбросить дробную часть?
Основные способы преобразования float64 в int в Go
В Go нет неявных преобразований между числовыми типами, поэтому для конвертации float64 в int нужно всегда использовать явное приведение. Однако простое int(f) выполняет усечение в сторону нуля (truncation), что подходит не для всех задач. Рассмотрим все основные подходы.
1. Простое приведение типа (усечение)
package main
import "fmt"
func main() {
f := 19.99
result := int(f) // отбрасывает .99
fmt.Println(result) // 19
f2 := -19.99
result2 := int(f2) // отбрасывает .99
fmt.Println(result2) // -19 (усечение в сторону нуля)
}
Работает быстро, но просто отбрасывает дробную часть. Для положительных чисел это эквивалентно floor, для отрицательных — ceil.
2. Округление до ближайшего целого (math.Round)
import "math"
func main() {
f := 19.99
result := int(math.Round(f)) // округление по правилам математики
fmt.Println(result) // 20
f2 := 19.49
result2 := int(math.Round(f2))
fmt.Println(result2) // 19
f3 := -19.99
result3 := int(math.Round(f3))
fmt.Println(result3) // -20
}
math.Round округляет до ближайшего целого, при .5 округляет от нуля (banker's rounding не используется). Результат — float64, поэтому нужно дополнительно привести к int.
3. Пол (math.Floor) и потолок (math.Ceil)
import "math"
func main() {
f := 19.99
floor := int(math.Floor(f)) // наибольшее целое, не превышающее f
fmt.Println(floor) // 19
ceil := int(math.Ceil(f)) // наименьшее целое, не меньшее f
fmt.Println(ceil) // 20
// Для отрицательных чисел:
f2 := -19.99
fmt.Println(int(math.Floor(f2))) // -20 (вниз)
fmt.Println(int(math.Ceil(f2))) // -19 (вверх)
}
4. Усечение в сторону нуля (math.Trunc)
import "math"
func main() {
f := 19.99
result := int(math.Trunc(f)) // то же самое, что int(f)
fmt.Println(result) // 19
f2 := -19.99
result2 := int(math.Trunc(f2))
fmt.Println(result2) // -19
}
math.Trunc явно показывает намерение — отбросить дробную часть. Результат тот же, что и при прямом приведении.
Важные подводные камни
- Переполнение (overflow): Если
float64содержит число, выходящее за пределыint(например, 1e20), результат приведения будет неопределённым. На 64-битной системеint— это 64 бита, максимальное значение ≈ 9.22e18. Всегда проверяйте диапазон. - NaN и Inf: Если
float64равенmath.NaN()илиmath.Inf(1), приведение кintдаст неопределённый результат (обычно минимальное значение int). Лучше проверять черезmath.IsNaNилиmath.IsInf. - Размер int: На 32-битных системах
int— 32 бита. Если ваш код должен быть кроссплатформенным, используйтеint64илиint32явно. - Потеря точности:
float64может точно представить только целые числа до 2^53. При больших значениях возможна потеря точности ещё до приведения.
Безопасное преобразование с проверкой
import (
"fmt"
"math"
)
func floatToIntSafe(f float64) (int, error) {
if math.IsNaN(f) || math.IsInf(f, 0) {
return 0, fmt.Errorf("cannot convert NaN or Inf to int")
}
if f > math.MaxInt || f < math.MinInt {
return 0, fmt.Errorf("float64 value %f out of int range", f)
}
return int(math.Round(f)), nil // или math.Trunc, math.Floor по необходимости
}
func main() {
result, err := floatToIntSafe(19.99)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println(result) // 20
}
}
Выбирайте метод в зависимости от задачи: для простого отбрасывания — int(f), для округления — int(math.Round(f)), для пола/потолка — соответствующие функции из пакета math. И всегда помните о проверке границ!
Альтернативный подход: использование пакета strconv
Если вам нужно преобразовать float64 в строку, а затем в целое число (например, для форматирования или парсинга), можно использовать пакет strconv. Это даёт больше контроля над округлением и форматом.
import (
"fmt"
"strconv"
)
func main() {
f := 19.99
// Округление до 0 знаков после запятой (отбрасывает дробную часть)
s := strconv.FormatFloat(f, 'f', 0, 64)
result, _ := strconv.Atoi(s)
fmt.Println(result) // 19
// Использование 'f' с явным округлением (по умолчанию Round half away from zero)
s2 := strconv.FormatFloat(f, 'f', 0, 64) // 0 знаков после запятой
result2, _ := strconv.Atoi(s2)
fmt.Println(result2) // 20? Нет, это будет 19, потому что FormatFloat с 'f' и точностью 0 отбрасывает дробную часть
// Для округления используйте 'f' с точностью -1 (самый короткий), но это не даст округления
// Лучше использовать fmt.Sprintf с форматом "%.0f"
s3 := fmt.Sprintf("%.0f", f) // округление до ближайшего целого
result3, _ := strconv.Atoi(s3)
fmt.Println(result3) // 20
}
Недостаток: этот подход медленнее, чем использование math, и добавляет лишние преобразования строк. Используйте его только если вам нужно дополнительно форматировать число (например, добавить разделители или знак валюты). Для простого округления лучше применять math.Round.
Совет: Если вы работаете с большими объёмами данных, избегайте strconv для массовой конвертации — используйте прямой вызов int(math.Round(f)).