Golang 舍入到最接近的 0.05 [英] Golang Round to Nearest 0.05

查看:30
本文介绍了Golang 舍入到最接近的 0.05的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am looking for a function to round to the nearest 0.05 in Golang. The end result of using the function must always be a factor of 0.05.

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

Here are some examples of outputs for the function I am looking for: (The function Round doesn't exist yet, I am hoping it can be included in the answer)

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

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

推荐答案

前言: 我在 github.com/icza/gox,参见 mathx.Round().

Go 1.10 已经发布,它增加了一个math.Round() 功能.这个函数四舍五入到最接近的整数(这基本上是一个四舍五入到最近的 1.0" 操作),使用它我们可以很容易地构造一个四舍五入到我们选择的单位的函数:

Go 1.10 has been released, and it adds a math.Round() function. This function rounds to the nearest integer (which is basically a "round to nearest 1.0" operation), and using that we can very easily construct a function that rounds to the unit of our choice:

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() 函数背后的逻辑.此处用于教育目的.

The original answer follows which was created before Go 1.10 when no math.Round() existed, and which also details the logic behind our custom Round() function. It's here for educational purposes.

在 Go1.10 之前的时代,没有 math.Round().但是...

In the pre-Go1.10 era there was no math.Round(). But...

舍入任务可以通过 float64 => int64 转换轻松实现,但必须注意浮点到 int 的转换不是舍入而是保持整数部分(详见Go: Converting float64 to int with multiplier).

Rounding tasks can easily be implemented by a float64 => int64 converison, but care must be taken as float to int conversion is not rounding but keeping the integer part (see details in 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:

Result is 12 in both cases, the integer part. To get the rounding "functionality", simply add 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:

So far so good. But we don't want to round to integers. If we'd wanted to round to 1 fraction digit, we would multiply by 10 before adding 0.5 and converting:

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:

So basically you multiply by the reciprocal of the unit you want to round to. To round to 0.05 units, multiply by 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 将 float64 转换为 int 错误.

Note that rounding 3.232 with unit=0.05 will not print exactly 3.25 but 0.35000000000000003. This is because float64 numbers are stored using finite precision, called the IEEE-754 standard. For details see Golang converting float64 to int error.

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

Also note that unit may be "any" number. If it's 1, then Round() basically rounds to nearest integer number. If it's 10, it rounds to tens, if it's 0.01, it rounds to 2 fraction digits.

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

Also note that when you call Round() with a negative number, you might get surprising result:

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>-1.6; 而1.6的整数部分是1,小于1.6).

This is because –as said earlier– conversion is keeping the integer part, and for example integer part of -1.6 is -1 (which is greater than -1.6; while integer part of 1.6 is 1 which is less than 1.6).

如果你想让 -0.363636 变成 -0.35 而不是 -0.30,那么在负数的情况下添加 -0.5 而不是 0.5Round() 函数中.查看我们改进的 Round2() 函数:

If you want -0.363636 to become -0.35 instead of -0.30, then in case of negative numbers add -0.5 instead of 0.5 inside the Round() function. See our improved Round2() function:

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,您建议将其格式化并重新解析,如下所示:

To address your comment: because you don't "like" the non-exact 0.35000000000000003, you proposed to format it and re-parse it like:

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

这种看似"的结果与打印结果完全相同,它给出了 0.35.

And this "seemingly" results in the exact result as printing it gives 0.35 exactly.

但这只是一种错觉".由于 0.35 不能使用 IEEE-754 标准用有限位表示,所以不管你用数字做什么,如果你把它存储在 float64 类型的值中,它不会完全是 0.35(但 IEEE-754 数字非常接近它).你看到的是 fmt.Println() 将它打印为 0.35 因为 fmt.Println() 已经做了一些四舍五入.

But this is just an "illusion". Since 0.35 cannot be represented with finite bits using IEEE-754 standard, doesn't matter what you do with the number, if you store it in a value of type float64, it won't be exactly 0.35 (but an IEEE-754 number being very close to it). What you see is fmt.Println() printing it as 0.35 because fmt.Println() already does some rounding.

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

But if you attempt to print it with higher precision:

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

Output: it's not nicer (might be even uglier): try it on the Go Playground:

0.349999999999999977795539507497
3.250000000000000000000000000000
0.500000000000000000000000000000

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

Note that on the other hand 3.25 and 0.5 are exact because they can be represented with finite bits exactly, because representing in binary:

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

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

What's the lesson? It's not worth formatting and re-parsing the result, as it won't be exact either (just a different float64 value which –according to default fmt.Println() formatting rules– might be nicer in printing). If you want nice printed format, just format with precision, like:

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 上试试):

And it will be exact (try it on the Go Playground):

0.350
3.250
0.500

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

Or just multiply them by 100 and work with integer numbers, so that no representation or rounding error may occur.

这篇关于Golang 舍入到最接近的 0.05的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆