Golangで0.05に最も近い値に丸める関数を探しています。関数を使用した最終結果は、常に0.05倍でなければなりません。
ここに私が探している関数の出力の例をいくつか示します:(関数Roundはまだ存在していません。答えに含めることができると期待しています)
Round(0.363636) // 0.35
Round(3.232) // 3.25
Round(0.4888) // 0.5
私は今、年齢を探し回っていますが、答えが見つかりませんでした。
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.232
でunit=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.6
は1
で、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.25
と0.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
を掛けて整数値を処理するだけで、表現や丸めエラーが発生しないようにすることができます。