前言:我在github.com/icza/gox发布了这个实用程序,见mathx.Round()。
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 上试试。
原始答案是在 Go 1.10 之前创建的,当时不存在 math.Round(),并且还详细说明了我们自定义 Round() 函数背后的逻辑。此处用于教育目的。
在 Go1.10 之前的时代,没有 math.Round()。但是……
舍入任务可以通过float64 => int64 转换轻松实现,但必须注意浮点到整数的转换不是舍入而是保留整数部分(详见Go: Converting float64 to int with multiplier)。
例如:
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 个小数位,我们会在添加 0.5 并转换之前乘以 10:
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上的例子。
请注意,用unit=0.05 舍入3.232 不会精确打印3.25,而是0.35000000000000003。这是因为float64 数字是使用有限精度存储的,称为IEEE-754 标准。详情见Golang converting float64 to int error。
另请注意,unit 可能是“任何”数字。如果是1,那么Round() 基本上会四舍五入到最接近的整数。如果是10,则四舍五入到十位,如果是0.01,则四舍五入到2位小数。
另请注意,当您使用负数调用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 的整数部分是 @987654369 @ 小于 1.6)。
如果您希望 -0.363636 变为 -0.35 而不是 -0.30,则在出现负数的情况下,在 Round() 函数内添加 -0.5 而不是 0.5。查看我们改进的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
编辑:
为了解决您的评论:因为您不“喜欢”不精确的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 并使用整数,这样就不会出现表示或舍入错误。