Осторожно! Округление в Go может вас удивить!
Представьте, что вам нужно округлить число до двух знаков после запятой. Вы пишете на Go простую и, казалось бы, логичную функцию:
func RoundToTwo(num float64) float64 {
return math.Round(num*100) / 100
}
Вы тестируете её на числе 1.005, ожидая получить 1.01. Но результат повергает в шок: функция возвращает 1.00
Почему так происходит?
Всё дело в природе чисел с плавающей запятой. Тип float64 не может точно представить десятичную дробь 1.005. В памяти компьютера это значение хранится как приблизительное двоичное представление, например — 1.0049999999999998934.
Давайте разберём по шагам, что происходит внутри функции:
-
num * 100— Мы умножаем1.004999на100и получаем результат =100.49999999999998934, а не идеальные100.5. -
math.Floor(...): Функцияmath.Floor()возвращает наибольшее целое число, меньшее или равное аргументу. Результат:100.0(так как 100.49... меньше, чем 100.5). -
Далее
100.0делим на100и получаем результат =1.00.
Вывод
Наивное округление через умножение и math.Floor (или math.Round) ненадёжно из-за погрешности представления чисел с плавающей запятой. Это классическая проблема, характерная не только для Go, а для большинства языков программирования, использующих стандарт IEEE 754.
Как решать проблему?
Для финансовых расчётов или там, где важна точность, следует использовать специальные пакеты для десятичной арифметики, например, shopspring/decimal, или округлять числа с помощью форматирования и парсинга строк.
s := fmt.Sprintf("%.2f", num)
f, _ := strconv.ParseFloat(s, 64)
return f
p.s. Я лично решил проблему как раз таки конвертом числа в строку, а далее уже обратный парсинг строки во float64 таким образом:
Комментарии
0
Ты: ...
Пока нет комментариев. Будь первым.