Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Addition is Not Communicative

2025年10月01日に「Dev.to」が公開したITニュース「Addition is Not Communicative」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

浮動小数点数の計算では、各加算の度に値が丸められるため、加算順序が結果に影響する。特に、大きな数と小さな数を足す際、小さい数が丸められて無視される場合がある。高精度な計算では、この特性を理解し、順序に十分注意する必要がある。

出典: Addition is Not Communicative | Dev.to公開日:

ITニュース解説

コンピュータの世界では、私たちが普段数学で使っている数の計算と、少し異なる振る舞いをすることがある。特に、「加算」という非常に基本的な計算でさえ、その順序が結果に影響を与える場合があるという事実は、システムエンジニアを目指す初心者にとって驚きかもしれない。これは、コンピュータが実数を表現する方法である「浮動小数点数」とその「丸め処理」に起因する現象である。

私たちが数学で当たり前だと考える「加算の交換法則」(A + B = B + A)や「結合法則」((A + B) + C = A + (B + C))は、正確な数学の世界では常に成り立つ。しかし、コンピュータが扱う浮動小数点数の世界では、これらの法則が常に成り立つとは限らないのだ。これは、IEEE-754という国際規格で定められた浮動小数点数の表現方法と、それに伴う計算の仕組みに理由がある。

浮動小数点数は、限られたビット数で非常に広範囲の実数を表現するために使われる。しかし、有限のビット数ではすべての実数を正確に表現することはできない。例えば、無限に続く小数(例:1/3 = 0.333...)や、コンピュータが割り当てられたメモリ空間で表現できないような微細な差を持つ数値は、最も近い「表現可能な値」に近似されることになる。この近似のプロセスを「丸め(rounding)」と呼ぶ。

問題は、この丸め処理が各演算、つまり一つ一つの加算や乗算の後に行われる点にある。正確な計算結果が得られたとしても、それが浮動小数点数として表現可能な範囲を超えていたり、特定のフォーマットに収まらなかったりする場合、コンピュータはその結果を最も近い表現可能な値に「丸めて」保存する。この「丸め」が、加算の順序が結果に影響を与える主な原因となる。特に、非常に大きな数と非常に小さな数を混ぜて計算するような、高精度を要するタスクでは、この影響が無視できない場合がある。

具体的な例で考えてみよう。記事では二つの加算の順序が異なる計算が示されている。 一つ目は「100,000,000(1e8)に1を足し、そこから100,000,000を引く」という計算((1e8 + 1) - 1e8)。 二つ目は「100,000,000から100,000,000を引いて、そこに1を足す」という計算((1e8 - 1e8) + 1)。 数学的にはどちらも結果は1になるはずだ。しかし、浮動小数点数で計算すると、一つ目の結果は0になり、二つ目の結果は1になることがある。なぜこのような違いが生まれるのだろうか。

まず、浮動小数点数の加算の仕組みを理解する必要がある。浮動小数点数は、一般的に符号部、指数部、仮数部(または分数部)の三つの部分で構成される。例えば、FP32と呼ばれる単精度浮動小数点数では、1ビットが符号、8ビットが指数、23ビットが仮数に割り当てられ、さらに暗黙の1ビット(先行ビット)を持つ。これにより、「符号 × 1.仮数部 × 2^指数部」のような形で数を表現する。

加算を行う際、コンピュータはまず、二つの数のうち指数部がより大きい方の形式に合わせる。これは、小数点の位置を揃えるようなものだ。例えば、1e8(10の8乗)と1を足す場合、1e8の指数部が圧倒的に大きいため、1e8の指数部の形式に合わせた加算が行われる。

1e8は指数部が「2^26」に相当する大きな数である。この大きな指数部に合わせると、浮動小数点数で表現できる最小の間隔(ULP: Unit in the Last Place)が大きくなる。具体的には、この指数部2^26のフォーマットでは、表現可能な最小間隔が「2の(26-23)乗」、つまり「2の3乗 = 8」になる。これは、そのフォーマットにおいて、8より小さい差は表現できず、最も近い8の倍数に丸められてしまうことを意味する。

ここで、一つ目の計算「(1e8 + 1) - 1e8」を見てみよう。 まず、「1e8 + 1」の計算が行われる。1e8の指数部2^26に合わせると、最小間隔が8になるため、1は8よりも小さい値として扱われる。この結果、1は表現可能な最小間隔に満たないため、最も近い表現可能な値である0に丸められてしまうのだ。 つまり、「1e8 + 1」は浮動小数点数演算においては実質的に「1e8 + 0」となり、結果は1e8となる。 次に、この結果から1e8を引くので、「1e8 - 1e8」となり、最終的な結果は0となる。数学的な期待値の1とは異なる結果だ。

次に、二つ目の計算「(1e8 - 1e8) + 1」を見てみよう。 まず、「1e8 - 1e8」の計算が行われる。同じ数同士の引き算なので、結果は正確に0になる。 次に、この0に1を足す。つまり「0 + 1」となり、この計算には丸め処理が必要なく、正確に1という結果が得られる。 このように、加算の順序を変えるだけで、同じ数値を使っているにもかかわらず、最終的な結果が0と1という異なる値になってしまうことがわかる。

この現象は、浮動小数点数演算が本質的に持つ性質であり、計算の精度を保つ上で非常に重要となる。プログラミングにおいては、特に科学技術計算、金融計算、シミュレーションなど、高い精度が求められる場面で、このような浮動小数点数の特性を理解し、計算順序を慎重に検討する必要がある。例えば、小さな数をまとめてから大きな数と加算する、といった工夫で精度を保つことができる場合もある。コンピュータが現実世界の実数を完全に表現できるわけではないという事実と、それゆえに生じる丸め誤差の影響を認識することが、システムエンジニアとして高品質なソフトウェアを開発する上で不可欠な知識である。

関連コンテンツ