Golang 四舍五入到最接近的 0.05

Golang Round to Nearest 0.05

我正在 Golang 中寻找一个四舍五入到最接近 0.05 的函数。 使用该函数的最终结果必须始终是 0.05 的因数。


以下是我正在寻找的函数的一些输出示例: (目前还没有Round这个函数,希望能包含在答案中)

Round(0.363636) // 0.35
Round(3.232)    // 3.25
Round(0.4888)   // 0.5

我找了好久都没有找到答案。

前言: 我在 github.com/icza/gox, see mathx.Round().

发布了这个实用程序

Go 1.10 已经发布,它增加了一个math.Round() 功能。这个函数四舍五入到最接近的整数(这基本上是一个 "round to nearest 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 转换轻松实现,但必须小心,因为 float 到 int 的转换 不是舍入而是保留整数部分(详见 )。

例如:

var f float64
f = 12.3
fmt.Println(int64(f)) // 12
f = 12.6
fmt.Println(int64(f)) // 12

两种情况下的结果都是 12,整数部分。要获得舍入 "functionality",只需添加 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 standard. For details see .

另请注意,unit 可能是 "any" 数字。如果是 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.5Round()函数。查看我们改进的 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

编辑:

为了解决您的评论:因为您没有 "like" 非精确的 0.35000000000000003,您建议对其进行格式化并重新解析,例如:

formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)

而这个 "seemingly" 的结果与打印结果完全一样 0.35

但这只是一个"illusion"。由于 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.250.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 并使用整数,这样就不会出现表示或舍入错误。