dpzlz08480 2016-09-17 08:19
浏览 51
已采纳

高朗舍入到最近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.


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

I have searched around for ages now and haven't found any answers.

  • 写回答

1条回答 默认 最新

  • dongzong7467 2016-09-17 08:57
    关注

    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
    }
    

    Testing it:

    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
    

    Try it on the Go Playground.

    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.


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

    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).

    For example:

    var f float64
    f = 12.3
    fmt.Println(int64(f)) // 12
    f = 12.6
    fmt.Println(int64(f)) // 12
    

    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
    

    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
    

    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
    

    Wrapping this into a function:

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

    Using it:

    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
    

    Try the examples on the Go Playground.

    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.

    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.

    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
    

    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).

    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
    }
    

    And using it:

    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
    

    EDIT:

    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)
    

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

    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))
    

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

    0.349999999999999977795539507497
    3.250000000000000000000000000000
    0.500000000000000000000000000000
    

    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
    

    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
    }
    

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

    0.350
    3.250
    0.500
    

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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 wireshark抓不到vlan
  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上
  • ¥15 c程序不知道为什么得不到结果
  • ¥40 复杂的限制性的商函数处理
  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来