Math Golang四舍五入至最接近的0.05

Math Golang四舍五入至最接近的0.05,math,go,rounding,Math,Go,Rounding,我正在寻找一个函数,将其四舍五入到Golang中最接近的0.05。 使用该函数的最终结果必须始终为系数0.05 以下是我正在寻找的函数的一些输出示例: (功能回合还不存在,我希望它能包含在答案中) 我已经搜索了很多年了,没有找到任何答案。前言:我在年发布了这个实用程序,请参阅 Go 1.10已经发布,它添加了一个函数。此函数舍入到最接近的整数(基本上是“舍入到最接近的1.0”操作),使用它,我们可以非常轻松地构造一个函数,舍入到我们选择的单位: func Round(x, unit floa

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


以下是我正在寻找的函数的一些输出示例: (功能回合还不存在,我希望它能包含在答案中)


我已经搜索了很多年了,没有找到任何答案。

前言:我在年发布了这个实用程序,请参阅


Go 1.10已经发布,它添加了一个函数。此函数舍入到最接近的整数(基本上是“舍入到最接近的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 1.10之前创建的,当时不存在
math.Round()
,它还详细说明了自定义
Round()
函数背后的逻辑。这里是为了教育目的


在Go1.10之前的时代,没有
math.Round()
。但是

舍入任务可以通过
float64
=>
int64
转换轻松实现,但必须小心,因为float-to-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个小数位数,我们将在添加
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
}
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(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
试一下上面的例子

请注意,将
3.232
四舍五入为
unit=0.05
不会准确打印
3.25
,而是
0.3500000000000003
。这是因为
float64
数字是使用称为标准的有限精度存储的。有关详细信息,请参阅

还要注意,
单元
可以是“任意”数字。如果它是
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
,则如果是负数,则在
Round()
函数中添加
-0.5
而不是
0.5
。请参阅我们改进的
Round2()
函数:

func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}
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.3500000000000003
,所以您建议将其格式化并重新解析为:

formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)
这种“表面上”的结果与打印时给出的
0.35
结果完全一致

但这只是一种“错觉”。由于使用IEEE-754标准,
0.35
不能用有限位表示,因此不管您如何处理数字,如果您将其存储在
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))
输出:不太好(可能更难看):试一下:

请注意,另一方面,
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
}
而且它将是精确的(在屏幕上尝试):


或者将它们乘以
100
,然后处理整数,这样就不会出现表示或舍入错误。

我在网上找到了一个,但它与我在问题中所问的不完全一样:/Related:如果你有一个舍入函数可以给出正确的结果,那么你所需要做的就是通过截断它来管理结果的精度。一种笨手笨脚的方法(不是边缘测试)正如公认的答案所说,将二进制浮点值四舍五入到小数点后的某个数字(0除外)并不特别有用。例如,无法准确表示值
0.05
。在64位IEEE浮点中,它可能存储为
0.05000000000000002775575615628913590791702270578125
。在大多数情况下,保持浮点值的全精度,只有在执行输出或转换为字符串时才对其进行四舍五入。这不过是纯粹的天才。谢谢你,请看编辑关于在负数上应用它。是的,我明白了,谢谢。我已经编辑了你的函数来解决0.363636=0.3500000000000003的问题,任何有兴趣的人都可以在上找到这个函数。@格式化和重新解析只会给你带来“错觉”。见编辑后的答案。不必这样做。@icza检查溢出情况如何?
0.350
3.250
0.500