Ответ 1
Go 1.10 был выпущен, и он добавляет функцию math.Round()
. Эта функция округляется до ближайшего целого числа (которое в основном представляет собой операцию "округление до ближайшей версии 1.0" ), и с помощью этого мы можем легко построить функцию, которая округляется до единицы по нашему выбору:
func Round(x, unit float64) float64 {
return math.Round(x/unit) * unit
}
Тестирование:
fmt.Println(Round(0.363636, 0.05)) // 0.35
fmt.Println(Round(3.232, 0.05)) // 3.25
fmt.Println(Round(0.4888, 0.05)) // 0.5
fmt.Println(Round(-0.363636, 0.05)) // -0.35
fmt.Println(Round(-3.232, 0.05)) // -3.25
fmt.Println(Round(-0.4888, 0.05)) // -0.5
Попробуйте на Go Playground.
Исходный ответ следует за тем, который был создан в эпоху, когда не существует math.Round()
, а также детализирует логику нашей пользовательской функции Round()
. Это здесь для образовательных целей.
В эпоху pre-Go1.10 не было math.Round()
. Но...
Задачи округления могут быть легко реализованы с помощью преобразования float64
= > int64
, но следует позаботиться о том, чтобы преобразование с плавающей точкой в int не округлялось, а сохраняло целочисленную часть (подробности см. в Go: преобразование float64 в int с множителем).
Например:
var f float64
f = 12.3
fmt.Println(int64(f)) // 12
f = 12.6
fmt.Println(int64(f)) // 12
Результат 12
в обоих случаях - целая часть. Чтобы получить функциональность округления, просто добавьте 0.5
:
f = 12.3
fmt.Println(int64(f + 0.5)) // 12
f = 12.6
fmt.Println(int64(f + 0.5)) // 13
Пока все хорошо. Но мы не хотим округлять до целых чисел. Если бы мы хотели округлить до 1 дробной цифры, мы бы умножали на 10 до добавления 0.5
и конвертировали:
f = 12.31
fmt.Println(float64(int64(f*10+0.5)) / 10) // 12.3
f = 12.66
fmt.Println(float64(int64(f*10+0.5)) / 10) // 12.7
Таким образом, в основном вы умножаетесь на обратную часть единицы, к которой вы хотите округлить. Чтобы округлить до 0.05
единиц, умножьте на 1/0.05 = 20
:
f = 12.31
fmt.Println(float64(int64(f*20+0.5)) / 20) // 12.3
f = 12.66
fmt.Println(float64(int64(f*20+0.5)) / 20) // 12.65
Включение этого в функцию:
func Round(x, unit float64) float64 {
return float64(int64(x/unit+0.5)) * unit
}
Используя его:
fmt.Println(Round(0.363636, 0.05)) // 0.35
fmt.Println(Round(3.232, 0.05)) // 3.25
fmt.Println(Round(0.4888, 0.05)) // 0.5
Попробуйте примеры на Go Playground.
Обратите внимание, что округление 3.232
с помощью unit=0.05
не будет точно печатать 3.25
, а 0.35000000000000003
. Это связано с тем, что цифры float64
сохраняются с использованием конечной точности, называемой IEEE-754. Подробнее см. Golang, конвертирующий float64 в int error.
Также обратите внимание, что unit
может быть "любым" числом. Если он 1
, то Round()
в основном округляется до ближайшего целого числа. Если он 10
, он округляется до десятков, если он 0.01
, округляет до двух цифр фракции.
Также обратите внимание, что когда вы вызываете Round()
с отрицательным числом, вы можете получить неожиданный результат:
fmt.Println(Round(-0.363636, 0.05)) // -0.3
fmt.Println(Round(-3.232, 0.05)) // -3.2
fmt.Println(Round(-0.4888, 0.05)) // -0.45
Это связано с тем, что, как сказано ранее, преобразование сохраняет целочисленную часть, и, например, целая часть -1.6
равна -1
(которая больше, чем -1.6
, а целая часть 1.6
равна 1
, который меньше 1.6
).
Если вы хотите, чтобы -0.363636
стал -0.35
вместо -0.30
, тогда в случае отрицательных чисел добавьте -0.5
вместо 0.5
внутри функции Round()
. См. Нашу улучшенную функцию Round2()
:
func Round2(x, unit float64) float64 {
if x > 0 {
return float64(int64(x/unit+0.5)) * unit
}
return float64(int64(x/unit-0.5)) * unit
}
И используя его:
fmt.Println(Round2(-0.363636, 0.05)) // -0.35
fmt.Println(Round2(-3.232, 0.05)) // -3.25
fmt.Println(Round2(-0.4888, 0.05)) // -0.5
EDIT:
Чтобы ответить на ваш комментарий: потому что вы не "любите" неточное 0.35000000000000003
, вы предложили отформатировать его и повторно разобрать его так:
formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)
И это "похоже" приводит к точному результату, так как печать дает именно 0.35
.
Но это всего лишь "иллюзия". Поскольку 0.35
не может быть представлен конечными битами с использованием стандарта IEEE-754, не имеет значения, что вы делаете с номером, если вы храните его в значении типа float64
, это будет не точно 0.35
( но номер IEEE-754 очень близок к нему). То, что вы видите, fmt.Println()
печатает его как 0.35
, потому что fmt.Println()
уже делает некоторое округление.
Но если вы попытаетесь напечатать его с большей точностью:
fmt.Printf("%.30f\n", Round(0.363636, 0.05))
fmt.Printf("%.30f\n", Round(3.232, 0.05))
fmt.Printf("%.30f\n", Round(0.4888, 0.05))
Вывод: он не более приятный (может быть, даже уродливый): попробуйте на Go Playground:
0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000
Обратите внимание, что с другой стороны 3.25
и 0.5
точны, потому что они могут быть представлены конечными битами точно, потому что представление в двоичном формате:
3.25 = 3 + 0.25 = 11.01binary
0.5 = 0.1binary
Какой урок? Не стоит форматировать и повторно анализировать результат, так как он не будет точным (просто другое значение float64
, которое по правилам форматирования по умолчанию fmt.Println()
может быть приятнее при печати). Если вам нужен хороший печатный формат, просто форматируйте с точностью, например:
func main() {
fmt.Printf("%.3f\n", Round(0.363636, 0.05))
fmt.Printf("%.3f\n", Round(3.232, 0.05))
fmt.Printf("%.3f\n", Round(0.4888, 0.05))
}
func Round(x, unit float64) float64 {
return float64(int64(x/unit+0.5)) * unit
}
И это будет точно (попробуйте на Go Playground):
0.350
3.250
0.500
Или просто умножьте их на 100
и работайте с целыми числами, чтобы не возникало ошибки представления или округления.