はじめに
皆さんこんにちは!
アイレット株式会社の森田です。
今回は金額計算処理を書く際に重要となる浮動小数点型と固定小数点型の違いについて整理します。
結論
金額計算など少しでも算出結果がズレると困る計算を行う際には固定小数点型に型変換してから計算を行うべき
2進数と循環の問題
コンピュータの内部では、全てのデータが2進数に変換されています。
数値も例外ではなく、全てが0と1に解体されます。
ちなみに、2進数とは2増えたら桁が1つ繰り上がる数のことです。
10進数の整数は下記の方法で2進数に変換を行えます。
変換したい10進数の整数を、商が0になるまで繰り返し2で割ります。
↓
その都度発生する余り(0または1)を記録します。
↓
最後に求めた余りから順に、最初に求めた余りまで(下から上へ)並べたものが、変換後の2進数です。
例えば、10進数で10である数字は
10(10進数)
↓
10 ÷ 2 = 5(余り0)
5 ÷ 2 = 2(余り1)
2 ÷ 2 = 1(余り0)
1 ÷ 2 = 0(余り1)
↓
1010(2進数)
という計算になるので、10進数でいう10は、2進数では1010と表記できます。
整数値の10進数を2進数に変換する際には上記の変換方法で問題が生じることはありません。
しかし、小数の10進数を2進数に変換する際にはある問題が生じます。
それが「循環」です。
まず、小数の10進数を2進数に変換するには下記の手順を踏む必要があります。
変換したい10進数の小数の部分を、商の小数部分が0になるまで繰り返し2倍します。
↓
その都度発生する積の整数部分(0または1)を記録します。
↓
最初に求めた整数部分から順に、最後に求めた整数部分まで(上から下へ)並べたものが、変換後の2進数です。
例えば、10進数で0.125である数字は
0.125(10進数)
↓
0.125 × 2 = 0.25(整数部分は0)
0.25 × 2 = 0.5(整数部分は0)
0.5 × 2 = 1(整数部分は1)
↓
0.001(2進数)
という計算になるので、0.125という小数の10進数は0.001という小数の2進数に変換できます。
この例では循環の問題は生じませんでした。
では、10進数の0.3という数字ではどうでしょうか。
先ほどの例と同じように計算してみます。
0.3(10進数)
↓
0.3 × 2 = 0.6(整数部分は0)
0.6 × 2 = 1.2(整数部分は1)
0.2 × 2 = 0.4(整数部分は0)
0.4 × 2 = 0.8(整数部分は0)
0.8 × 2 = 1.6(整数部分は1)
0.6 × 2 = 1.2(整数部分は1)
0.2 × 2 = 0.4(整数部分は0)
0.4 × 2 = 0.8(整数部分は0)
0.8 × 2 = 1.6(整数部分は1)
0.6 × 2 = 1.2(整数部分は1)
…
↓
0.0100110011...(2進数)
という計算になります。
計算結果を見ると、小数第一位の0を除いて「1001」という数値列を繰り返していることがわかります。
この循環には終わりがありません。この循環を打ち切らないと無限にデータ量が増加していくため、データを保持することが出来ません。
そこで登場するのが固定小数点型と浮動小数点型です。
ちなみに、再度10進数に変換すると0.2998046875という数値になり、厳密には0.3にはなりません。
この辺りの問題については下記の記事が詳しいです。
「0.1 + 0.2 ≠ 0.3」を解明せよ
https://zenn.dev/sdb_blog/articles/01_plus_02_does_not_equal_03
固定小数点型と浮動小数点型
先ほどの計算で見た通り、 0.3のような数は2進数に変換すると無限に続く「循環小数」になってしまいました。
そこで、この無限に続く数をどのように有限の枠に収めるかというアプローチとして、浮動小数点型と固定小数点型が存在します。
浮動小数点型
浮動小数点型は、数値を符号(プラスかマイナスか)、仮数(有効数字)、指数(小数点の位置の3つのパートに分けて表現する方法です。
様々な数値を表現できるのがメリットですが、表現できる桁数(ビット数)に物理的な限界があるという致命的な特徴があります。
単精度浮動小数点型(float型)
データを32ビットで扱います。
有効桁数は10進数換算で約7桁です。
メモリ消費は少ないですが、誤差が大きくなりやすいため、金額計算には向きません。
倍精度浮動小数点型(double型)
データを64ビットで扱います。
有効桁数は10進数換算で約19〜20桁です。
単精度に比べて精度は高いですが、2進数で循環してしまうという根本的な問題は解決していません。
0.3 を表現する際、単精度よりは正確ですが、それでも末尾には微細な誤差が含まれています。
ちなみに、浮動小数点型のデータにおいてビット制約から漏れ出た数字に関しては一般的には最近接偶数方向丸めという方法によって丸められます。
- 最も近い浮動小数点数に丸める
- 端数が0.5より大きければ切り上げ、0.5より小さければ切り捨て
- 最も近い浮動小数点数が2つある場合は、仮数部の最下位の桁が偶数であるものを選択する
- 端数がちょうど0.5の場合は、丸め後、結果が偶数となる方へ丸める
「最近接遇数丸め」(https://scrapbox.io/mrsekut-p/%E6%9C%80%E8%BF%91%E6%8E%A5%E9%81%87%E6%95%B0%E4%B8%B8%E3%82%81 より引用、2025/12/17閲覧)
固定小数点型(decimal型)
固定小数点型は、あらかじめ小数点の位置を固定して扱う方法です。
浮動小数点型との最大の違いは、内部的に数値を整数として扱っている点です。
例えば、0.30 という値を扱う場合、「小数点を右に2つずらして 30(整数)として保存し、計算時は 1/100 倍として扱う」といった管理を行います。 整数である「30」は、2進数に変換しても循環しないため、浮動小数点型のような誤差が発生しません。
浮動小数点型: 0.3 → 2進数で無限に循環 → 桁数の限界で無理やりカット → 誤差発生
固定小数点型: 0.3 → 「3 × 0.1」のような形で管理 → 整数の「3」はきれいに変換可能 → 誤差なし
このように、固定小数点型(decimal型)を使用することで、桁数の限界による勝手な丸め込みを防ぎ、意図した通りの精度を担保することができます。
データ型を意識しないで金額計算処理を書くとどうなるか
一般的に金額計算をする際には固定小数点型を用いるのが良いとされています。
では、金額計算に浮動小数点型を用いるとどうなってしまうのでしょうか。
これまで述べて来た通り浮動小数点型では細かい単位で誤差が生じることがあります。
単発の計算では表面化しなくても、数千件のレコードを一気に加算したり、消費税計算などで複雑な端数処理を行ったりした際に、その微細な誤差が積み重なって整数単位の誤差として顕在化します。
システムの要件にもよりますが、金融システムにおいて整数単位の誤差は命取りになります。
そのため、金額計算処理を行う際には誤差の出にくい固定小数点型での計算を行うことが重要になってきます。
参考
「0.1 + 0.2 ≠ 0.3」を解明せよ
https://zenn.dev/sdb_blog/articles/01_plus_02_does_not_equal_03
最近接偶数丸め
https://scrapbox.io/mrsekut-p/%E6%9C%80%E8%BF%91%E6%8E%A5%E9%81%87%E6%95%B0%E4%B8%B8%E3%82%81