1. 導入
Go言語で小数を扱う際、最も頻繁に使用されるのがfloat64型です。しかし、浮動小数点数はコンピュータのメモリ上で2進数として扱われるため、10進数の計算では直感に反する誤差が生じることがあります。実務において、特に決済処理や厳密な数値計算を行う際にこの特性を理解していないと、重大なバグを引き起こす可能性があります。本稿では、float64の基本と、実務で安全に扱うためのポイントを解説します。
2. 基礎知識
float64は「倍精度浮動小数点数(Double-precision floating-point format)」と呼ばれ、IEEE 754標準に基づいています。Go言語において、ソースコード内に数値を直接記述(リテラル)して小数を扱った場合、デフォルトでfloat64型として解釈されます。
この型は64ビット(8バイト)のメモリを消費し、広範囲な数値を表現できますが、2進数で表現できない小数は「近似値」として保持されます。そのため、0.1 + 0.2 が正確に 0.3 にならないといった問題が発生します。
3. 実装/解決策
実務で浮動小数点数を扱う際の鉄則は「精度の高い計算にはfloat64を避ける」ことです。
・金融・決済系: 数値を整数(int64)に変換して扱う(例:円を銭に直す、または固定小数点数ライブラリを使用する)。
・科学技術計算: 誤差を許容した上で、比較演算には「非常に小さな値(イプシロン)」を用いた差分チェックを行う。
4. サンプルプログラム
以下は、float64の誤差が発生する例と、それを比較演算する際の安全な手法です。
package main
import (
"fmt"
"math"
)
func main() {
// 誤差の例: 0.1 + 0.2 は厳密には 0.30000000000000004 になる
a := 0.1
b := 0.2
sum := a + b
fmt.Printf("0.1 + 0.2 = %.20f\n", sum)
// float64の比較には == 演算子を使ってはいけない
// 代わりに「許容誤差(イプシロン)」を使って比較する
const epsilon = 1e-9
target := 0.3
// 差分が非常に小さければ「等しい」とみなす
if math.Abs(sum-target) < epsilon {
fmt.Println("実質的に等しいと判定されました")
} else {
fmt.Println("異なります")
}
}
5. 応用・注意点
・比較演算子(==, !=)の使用禁止: float64同士の比較に等価演算子を使うと、計算の順序の違いだけでfalseになることがあります。必ずmath.Abs(a - b) < epsilonのようなアプローチを採用してください。
・型変換のオーバーフロー: int型からfloat64へ変換する場合、非常に大きな整数だと精度が落ちて情報が欠落する可能性があります。大きな整数を扱う場合は注意が必要です。
・ライブラリの活用: もし厳密な十進数計算が必要な場合は、標準ライブラリではなく「shopspring/decimal」のような外部パッケージの利用を強く推奨します。これにより、浮動小数点特有の誤差問題から解放されます。
実務においては、float64が「何に使うべきか」を常に意識し、厳密さが求められる場所では迷わず適切な型やライブラリを選択するようにしましょう。

コメント