Golang Round最近的0.05

Aci*_*dic 28 math rounding go

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


以下是我正在寻找的函数的一些输出示例:(函数Round尚不存在,我希望它可以包含在答案中)

Round(0.363636) // 0.35
Round(3.232)    // 3.25
Round(0.4888)   // 0.5
Run Code Online (Sandbox Code Playgroud)

我现在已经搜索了很多年,并没有找到任何答案.

icz*_*cza 85

Go 1.10已经发布,它增加了一个math.Round()功能.此函数舍入到最接近的整数(基本上是"舍入到最接近的1.0"操作),使用它我们可以很容易地构造一个舍入到我们选择的单位的函数:

func Round(x, unit float64) float64 {
    return math.Round(x/unit) * unit
}
Run Code Online (Sandbox Code Playgroud)

测试它:

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
Run Code Online (Sandbox Code Playgroud)

Go Playground尝试一下.

原始答案是在Go 1.10之前创建的,当时不math.Round()存在,并且还详细说明了我们的自定义Round()函数背后的逻辑.它出于教育目的.


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

舍入任务可以通过float64=> int64converison 轻松实现,但必须小心,因为float到int转换不是舍入而是保留整数部分(请参阅Go中的详细信息:使用乘法器将float64转换为int).

例如:

var f float64
f = 12.3
fmt.Println(int64(f)) // 12
f = 12.6
fmt.Println(int64(f)) // 12
Run Code Online (Sandbox Code Playgroud)

结果是12在两种情况下,整数部分.要获得舍入"功能",只需添加0.5:

f = 12.3
fmt.Println(int64(f + 0.5)) // 12
f = 12.6
fmt.Println(int64(f + 0.5)) // 13
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但我们不想舍入到整数.如果我们想要舍入到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
Run Code Online (Sandbox Code Playgroud)

所以基本上你乘以你想要舍入的单位的倒数.要舍入到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
Run Code Online (Sandbox Code Playgroud)

将其包装成一个函数:

func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}
Run Code Online (Sandbox Code Playgroud)

使用它:

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
Run Code Online (Sandbox Code Playgroud)

试试Go Playground上的例子.

需要注意的是四舍五入3.232unit=0.05不准确打印3.25,但0.35000000000000003.这是因为float64数字是使用有限精度存储的,称为IEEE-754标准.有关详细信息,请参阅Golang将float64转换为int错误.

另请注意,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
Run Code Online (Sandbox Code Playgroud)

这是因为所述-as转换earlier-是保持整数部分,以及用于例如整数部分-1.6-1(其大于-1.6;而整数部分1.61小于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
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

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
Run Code Online (Sandbox Code Playgroud)

编辑:

为了解决你的评论:因为你没有"喜欢"不完全的评论0.35000000000000003,你建议格式化它并重新解析它:

formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)
Run Code Online (Sandbox Code Playgroud)

并且这种"看似"会产生确切的结果,因为它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))
Run Code Online (Sandbox Code Playgroud)

输出:它不是更好(可能更丑):在Go Playground尝试:

0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000
Run Code Online (Sandbox Code Playgroud)

另一方面请注意3.25并且0.5它们是精确的,因为它们可以用有限位完全表示,因为以二进制表示:

3.25 = 3 + 0.25 = 11.01binary
0.5 = 0.1binary
Run Code Online (Sandbox Code Playgroud)

这是什么教训?不值得格式化和重新解析结果,因为它也不准确(只是一个不同的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
}
Run Code Online (Sandbox Code Playgroud)

它将是准确的(在Go Playground尝试):

0.350
3.250
0.500
Run Code Online (Sandbox Code Playgroud)

或者只是将它们相乘100并使用整数,这样就不会出现任何表示或舍入错误.

  • 只不过是纯粹的天才.谢谢icza. (6认同)