web-dev-qa-db-ja.com

Golangを最も近い0.05に丸める

Golangで0.05に最も近い値に丸める関数を探しています。関数を使用した最終結果は、常に0.05倍でなければなりません。


ここに私が探している関数の出力の例をいくつか示します:(関数Roundはまだ存在していません。答えに含めることができると期待しています)

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

私は今、年齢を探し回っていますが、答えが見つかりませんでした。

28
Acidic

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 で試してください。

math.Round()が存在しないGo 1.10より前に作成されたオリジナルの回答と、カスタムRound()関数の背後にあるロジックの詳細が続きます。教育目的でここにあります。


Go1.10より前の時代には、math.Round()はありませんでした。しかし...

丸めタスクはfloat64 => int64変換によって簡単に実装できますが、floatからintへの変換は丸めではなく整数部分を保持するため、注意が必要です(詳細は Go:乗算器を使用してfloat64をintに変換する )を参照してください。

例えば:

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 の例を試してください。

3.232unit=0.05を丸めると、3.25ではなく0.35000000000000003が正確に出力されることに注意してください。これは、float64番号が IEEE-754 標準と呼ばれる有限精度を使用して保存されるためです。詳細については、 Golangからfloat64への変換エラー を参照してください。

また、unitは「任意の」番号である可能性があることに注意してください。 1の場合、Round()は基本的に最も近い整数に丸められます。 10の場合は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.61で、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()は既にいくつかの丸めを行っているため、fmt.Println()0.35として印刷します。

しかし、より高い精度で印刷しようとすると:

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

レッスンは何ですか?正確ではないため、結果を書式設定して再解析する価値はありません(デフォルトのfmt.Println()書式ルールに従って異なるfloat64値だけが印刷で優れている場合があります)。 Nice印刷フォーマットが必要な場合は、次のように正確にフォーマットしてください。

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を掛けて整数値を処理するだけで、表現や丸めエラーが発生しないようにすることができます。

83
icza