【问题标题】:Golang Round to Nearest 0.05Golang 舍入到最接近的 0.05
【发布时间】:2016-09-17 08:19:54
【问题描述】:

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


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

Round(0.363636) // 0.35
Round(3.232)    // 3.25
Round(0.4888)   // 0.5

我已经搜索了很长时间,但没有找到任何答案。

【问题讨论】:

  • 如果您有一个可以提供正确结果的舍入函数,您需要做的就是通过截断结果来管理结果的精度。一种笨拙的方法是play.golang.org/p/DRTTkQQI42(未经边缘测试)
  • 正如公认的答案所说,将二进制浮点值四舍五入到小数位数(0 以外)并不是特别有用。例如,值0.05 无法准确表示。在 64 位 IEEE 浮点中,它可能会存储为 0.05000000000000000277555756156289135105907917022705078125。在大多数情况下,保持浮点值全精度,仅在执行输出或转换为字符串时对其进行舍入。

标签: math go rounding


【解决方案1】:

前言:我在github.com/icza/gox发布了这个实用程序,见mathx.Round()


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 上试试。

原始答案是在 Go 1.10 之前创建的,当时不存在 math.Round(),并且还详细说明了我们自定义 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上的例子。

请注意,用unit=0.05 舍入3.232 不会精确打印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 的整数部分是 @987654369 @ 小于 1.6)。

如果您希望 -0.363636 变为 -0.35 而不是 -0.30,则在出现负数的情况下,在 Round() 函数内添加 -0.5 而不是 0.5。查看我们改进的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

但这只是一种“幻觉”。由于 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))

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

0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000

请注意,另一方面,3.250.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
}

而且会很准确(在Go Playground 上试试):

0.350
3.250
0.500

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

【讨论】:

  • 纯属天才。谢谢你,icza。
  • @Acidic 请参阅有关将其应用于负数的编辑。
  • 是的,我明白了,谢谢。我已经编辑了你的函数来解决 0.363636 = 0.35000000000000003 的问题,任何感兴趣的人都可以在play.golang.org/p/jxILFBYBEF 获得。
  • @Acidic 格式化和重新解析只会给您带来“错觉”。请参阅编辑后的答案。没有必要这样做。
  • @icza 检查溢出呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多