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

这是我要查找的功能的一些输出示例:
(Round函数尚不存在,我希望它可以包含在答案中)

1
2
3
Round(0.363636) // 0.35
Round(3.232)    // 3.25
Round(0.4888)   // 0.5

我已经搜索了很久了,但是没有找到任何答案。

  • 香港专业教育学院在网上找到了一个,但并不能完全满足我在问题中的要求:/
  • 相关:转到:使用乘数将float64转换为int
  • 如果您具有舍入功能可以给出正确的结果,那么您所要做的就是通过截断来管理结果精度。 play.golang.org/p/DRTTkQQI42(未经边缘测试)是一种for脚的方法
  • 正如公认的答案所说,将二进制浮点值四舍五入到小数点后一位(不是0)不是特别有用。 例如,值0.05不能准确表示。 在64位IEEE浮点数中,它可能会存储为0.05000000000000000277555756156289135105907917022705078125。 对于大多数目的,请使浮点值保持全精度,并且仅在执行输出或转换为字符串时才对它们进行舍入。

Go 1.10已发布,并添加了math.Round()函数。此函数四舍五入到最接近的整数(基本上是"四舍五入至1.0"运算),使用该函数,我们可以很容易地构造一个四舍五入到所选单位的函数:

1
2
3
func Round(x, unit float64) float64 {
    return math.Round(x/unit) * unit
}

测试它:

1
2
3
4
5
6
7
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的转换不是舍入而是保留整数部分(请参见Go:使用乘数将float64转换为int的详细信息)。

例如:

1
2
3
4
5
var f float64
f = 12.3
fmt.Println(int64(f)) // 12
f = 12.6
fmt.Println(int64(f)) // 12

在两种情况下,结果均为12的整数部分。要获取舍入的"功能",只需添加0.5

1
2
3
4
f = 12.3
fmt.Println(int64(f + 0.5)) // 12
f = 12.6
fmt.Println(int64(f + 0.5)) // 13

到目前为止,一切都很好。但是我们不想舍入到整数。如果要四舍五入到小数点后一位,则在添加0.5并进行转换之前,将其乘以10。

1
2
3
4
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

1
2
3
4
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

将其包装为一个函数:

1
2
3
func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}

使用它:

1
2
3
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标准的有限精度存储的。有关详细信息,请参见Golang将float64转换为int错误。

另请注意,unit可以是"任意"数字。如果它是1,则Round()基本上四舍五入为最接近的整数。如果为10,则四舍五入为十进制;如果为0.01,则四舍五入为2个小数位。

还要注意,当您用负数调用Round()时,可能会得到令人惊讶的结果:

1
2
3
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.30成为-0.35,则在负数的情况下,在Round()函数内添加-0.5而不是0.5。请参阅我们改进的Round2()函数:

1
2
3
4
5
6
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
}

并使用它:

1
2
3
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,因此建议将其格式化并重新解析为:

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

而且,这种"貌似"的结果与打印出来的结果完全一样,它给出了0.35

但这只是一个"幻想"。由于0.35不能使用IEEE-754标准用有限的位表示,因此对数字无所谓,如果将其存储在类型为float64的值中,则它不会完全是0.35(但IEEE-754编号非常接近)。您看到的是fmt.Println()将其打印为0.35,因为fmt.Println()已经进行了四舍五入。

但是,如果您尝试以更高的精度打印它:

1
2
3
4
5
6
fmt.Printf("%.30f
", Round(0.363636, 0.05))
fmt.Printf("%.30f
", Round(3.232, 0.05))
fmt.Printf("%.30f
", Round(0.4888, 0.05))

输出:不好(可能更难看):在Go Playground上尝试:

1
2
3
0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000

请注意,另一方面,3.250.5是精确的,因为它们可以精确地用有限的位表示,因为用二进制表示:

1
2
3.25 = 3 + 0.25 = 11.01binary
0.5 = 0.1binary

有什么教训?不值得格式化和重新解析结果,因为它也不是精确的(只是一个不同的float64值-根据默认的fmt.Println()格式设置规则-在打印中可能会更好)。如果您想要好的打印格式,则只需进行精确的格式化即可,例如:

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
    fmt.Printf("%.3f
", Round(0.363636, 0.05))
    fmt.Printf("%.3f
", Round(3.232, 0.05))
    fmt.Printf("%.3f
", Round(0.4888, 0.05))
}

func Round(x, unit float64) float64 {
    return float64(int64(x/unit+0.5)) * unit
}

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

1
2
3
0.350
3.250
0.500

或者只是将它们乘以100并使用整数,这样就不会发生任何表示或舍入错误。

  • 无非就是天才。 谢谢icza。
  • @Acidic请参见有关将其应用于负数的修改。
  • 是的,我明白了。 我已经编辑了您的函数来解决0.363636 = 0.35000000000000003的问题,该问题可以在play.golang.org/p/jxILFBYBEF中找到,以供有兴趣的人使用。
  • @Acidic格式化和重新解析仅给您带来"幻觉"。 参见编辑后的答案。 不需要这样做。
  • @icza怎么检查溢出?
  • @PickBoy该答案未涵盖该内容,因为该问题与舍入(错误)有关。