# 【Go】四舍五入在go语言中为何如此困难

<!--more-->

## Round 第一弹

stackoverflow 问题中的最佳答案首先获得我的关注，它在 mathx.Round 被开源，以下是代码实现：

``````//source: https://github.com/icza/gox/blob/master/mathx/mathx.go
package mathx

import "math"

// Round returns x rounded to the given unit.
// Tip: x is "arbitrary", maybe greater than 1.
// For example:
//     Round(0.363636, 0.001) // 0.364
//     Round(0.363636, 0.01)  // 0.36
//     Round(0.363636, 0.1)   // 0.4
//     Round(0.363636, 0.05)  // 0.35
//     Round(3.2, 1)          // 3
//     Round(32, 5)           // 30
//     Round(33, 5)           // 35
//     Round(32, 10)          // 30
//
// For details, see https://stackoverflow.com/a/39544897/1705598
func Round(x, unit float64) float64 {
return math.Round(x/unit) * unit
}``````

``````//source: https://play.golang.org/p/0uN1kEG30kI
package main

import (
"fmt"
"math"
)

func main() {
f := 12.15807659924030304
fmt.Println(Round(f, 0.0001)) // 12.158100000000001

f = 0.15807659924030304
fmt.Println(Round(f, 0.0001)) // 0.15810000000000002
}

func Round(x, unit float64) float64 {
return math.Round(x/unit) * unit
}``````

## 格式化与反解析

``````source: https://play.golang.org/p/jxILFBYBEF
package main

import (
"fmt"
"strconv"
)

func main() {
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
}

func Round(x, unit float64) float64 {
var rounded float64
if x > 0 {
rounded = float64(int64(x/unit+0.5)) * unit
} else {
rounded = float64(int64(x/unit-0.5)) * unit
}
formatted, err := strconv.ParseFloat(fmt.Sprintf("%.2f", rounded), 64)
if err != nil {
return rounded
}
return formatted
}``````

``````package main

import (
"fmt"
"strconv"
)

func main() {
f := 12.15807659924030304
fmt.Println(Round(f, 0.0001)) // 12.1581

f = 0.15807659924030304
fmt.Println(Round(f, 0.0001)) // 0.1581

fmt.Println(Round(0.363636, 0.0001)) // 0.3636
fmt.Println(Round(3.232, 0.0001))    // 3.232
fmt.Println(Round(0.4888, 0.0001))   // 0.4888
}

func Round(x, unit float64) float64 {
var rounded float64
if x > 0 {
rounded = float64(int64(x/unit+0.5)) * unit
} else {
rounded = float64(int64(x/unit-0.5)) * unit
}

var precision int
for unit < 1 {
precision++
unit *= 10
}

formatted, err := strconv.ParseFloat(fmt.Sprintf("%."+strconv.Itoa(precision)+"f", rounded), 64)
if err != nil {
return rounded
}
return formatted
}``````

## 大道至简

``````package main

import (
"fmt"

"github.com/thinkeridea/go-extend/exmath"
)

func main() {
f := 0.15807659924030304
fmt.Println(float64(int64(f*10000+0.5)) / 10000) // 0.1581
}``````

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

`unit` 参数和之前的概念不同了，保留一位小数 `uint =10`，只是整数 `uint=1`, 想对整数部分进行精度控制 `uint=0.01` 例如： `Round(1555.15807659924030304, 0.01) = 1600``Round(1555.15807659924030304, 1) = 1555``Round(1555.15807659924030304, 10000) = 1555.1581`

## 终极方案

``````//source: https://github.com/thinkeridea/go-extend/blob/main/exmath/round.go

package exmath

import (
"math"
)

// Round 四舍五入，ROUND_HALF_UP 模式实现
// 返回将 val 根据指定精度 precision（十进制小数点后数字的数目）进行四舍五入的结果。precision 也可以是负数或零。
func Round(val float64, precision int) float64 {
p := math.Pow10(precision)
return math.Floor(val*p+0.5) / p
}``````

## 总结

`Round` 功能虽简单，但是受到 `float` 精度影响，仍然有很多人在四处寻找稳定高效的算法，参阅了大多数资料后精简出 exmath.Round 方法，期望对其他开发者有所帮助，至于其精度使用了大量的测试用例，没有超过 `float` 精度范围时并没有出现精度问题，未知问题等待社区检验，具体测试用例参见 round_test

224 声望308 粉丝

0 条评论