二進浮動小数点数の加減算の結果が正確に表せる場合について

浮動小数点数同士の足し算や引き算の結果は、浮動小数点数で正確に表せるとは限りません。 そういった場合、近い値を持つ浮動小数点数に丸められます。

どういった場合に結果が浮動小数点数で正確に表せるか、という問題に対しては、以下のシンプルな結果がよく知られています。

「同符号の二つの浮動小数点数の差を求める場合、二数の比が二倍以内(0.5~2)に収まっていれば、必ず正確に表せる」

しかし、この結果は十分条件ではありますが、明らかに必要十分条件になっていません。 例えば、4.0-1.0=3.0ですが、4.01.0の比は二倍を超えているにもかかわらず結果の3.0は正確に表せそうです。

必要十分条件はどのようになっているのかを数式で考えるのは難しいので、どういった場合に足し算や引き算の結果が浮動小数点数で正確に表せるのかを可視化してみました。 画像が大きくなるのを防ぐため、符号1bit、指数部3bit、仮数部4bitの二進浮動小数点数を仮想的に考えて可視化しました。 画像の1ピクセルが特定の浮動小数点数と特定の浮動小数点数の引き算を行った場合に対応しています(224通り×224通り)。 黒になっている部分は、引き算の結果が浮動小数点数で正確に表せないところ、緑になっている部分は引き算の結果が浮動小数点数で正確に表せる部分です。 第一象限は正の浮動小数点数から正の浮動小数点数を引いた場合、第二象限は正の浮動小数点数から負の浮動小数点数を引いた場合に対応しています。

f:id:lpha_z:20210418230035p:plain

まず対角線部分(左下から右上)を見ると、緑で埋め尽くされている部分があることがわかります。 これが「二数の比が二倍以内に収まっていれば、必ず正確に表せる」という部分です。 実際にはもう少し広くとれそうだということがわかります。

次にもう一つの対角線部分(左上から右下)を見ると、市松模様になっています。 これは、1.0-(-1.1)=2.1みたいな繰り上がり(指数部の増加)が発生する場合、仮数部の最終桁の重みが2倍になることが原因です。 つまり、減算結果の最終桁が偶数にならないと、2倍になった最終桁の重みより小さい端数が出てしまって正確に表せないからです。

ところで、左上・右下を見るとその法則から外れてすべてが黒になっています。 これは、オーバーフロー(絶対値が大きすぎて浮動小数点数で表せる範囲を超えてしまう)が発生している部分です。

中央付近は非正規化数です。 非正規化数は実質固定小数点数なので計算結果は常に正確に表せます。

その他の部分で”ひげ”みたいなものが伸びているは、仮数部の下数ビットが全て0であるような数(仮数部が2Nの倍数になっている)の部分です。 こういった数は、絶対値が大きく離れた数と加減算しても、結果に端数が発生しないため、結果が浮動小数点数で正確に表せます。

ちなみに、「同符号の二つの浮動小数点数の差を求める場合、二数の比が二倍以内(0.5~2)に収まっていれば、必ず正確に表せる」という部分を青く塗ってみた図が以下になります。

f:id:lpha_z:20210418231241p:plain

確かに十分条件になっていることがわかります。 また、非正規化数の部分を除いた場合、この画像でユークリッド距離に対して凸になるように範囲を定めると、これ以上広くは取れなさそうなことがわかります。 マンハッタン距離なら、もうちょっと広く取れそうです。