我正在寻找一个在Golang 中舍入到最接近的0.05的函数。 使用该函数的最终结果必须始终为0.05。
以下是我正在寻找的功能的输出示例: (函数Round还没有存在,我希望它可以包含在答案中)
DecimalFormat formatter = new DecimalFormat("#.###");
我现在已经搜索了很多年,但没有找到任何答案。
答案 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.232
对unit=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
并使用整数,这样就不会出现任何表示或舍入错误。