Golang Round最近的0.05

时间:2016-09-17 08:19:54

标签: math go rounding

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

以下是我正在寻找的功能的输出示例: (函数Round还没有存在,我希望它可以包含在答案中)

DecimalFormat formatter = new DecimalFormat("#.###"); 

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

1 个答案:

答案 0 :(得分:75)

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()的情况下在Go 1.10之前创建的,并且还详细说明了我们的自定义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上的示例。

请注意,使用3.232unit=0.05进行舍入不会精确打印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 1小于1.6

如果您希望-0.363636成为-0.35而不是-0.30,那么如果是负数,请在{{1}内添加-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 ,您建议对其进行格式化并重新解析,如:

0.35000000000000003

这种“看似”会产生确切的结果,因为打印它会准确地给出formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)

但这只是一种“错觉”。由于0.35无法使用IEEE-754标准用有限位表示,因此如果将数字存储在0.35类型的值中,则无关紧要,它将不完全正确float64(但IEEE-754号码非常接近它)。你看到的是0.35将其打印为fmt.Println(),因为0.35已经进行了一些舍入。

但是如果你试图以更高的精度打印它:

fmt.Println()

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

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))

另请注意,另一方面0.349999999999999977795539507497 3.250000000000000000000000000000 0.500000000000000000000000000000 3.25是准确的,因为它们可以用有限位完全表示,因为用二进制表示:

0.5

教训是什么?不值得格式化和重新解析结果,因为它也不准确(只是一个不同的3.25 = 3 + 0.25 = 11.01binary 0.5 = 0.1binary 值 - 根据默认的float64格式化规则 - 可能在打印时更好。如果你想要很好的打印格式,只需要精确格式化,如:

fmt.Println()

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

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
}

或者只是将它们乘以0.350 3.250 0.500 并使用整数,这样就不会出现任何表示或舍入错误。