C・C++・Javaにおける浮動小数点演算と結合法則 ― 計算順序で結果が変わる理由
C、C++、Javaなどの言語では、浮動小数点数を使ったさまざまな数学演算を行います。本記事では、浮動小数点数が数学における「結合法則」((a + b) + c = a + (b + c))に従うのかどうかを、実際のコードで検証します。
結論から言うと、答えは「NO」です。場合によっては、浮動小数点数は結合法則を満たしません。以下で具体的な例を見ていきましょう。
サンプルコード
#include<iostream>
using namespace std;
main() {
float x = -500000000;
float y = 500000000;
float z = 1;
cout << "x + (y + z) is: " << x + (y + z) << endl;
cout << "(x + y) + z is " << (x + y) + z << endl;
}
実行結果
x + (y + z) is: 0 (x + y) + z is 1
なぜ結果が異なるのか?
数学的に考えれば、どちらの式も常に「1」になるはずです。しかし、実際の実行結果は異なっています。この原因は、浮動小数点数特有の「丸め誤差(ラウンドオフ)」にあります。
最初の式「x + (y + z)」では、まず括弧内の「500000000 + 1」が計算されます。ところが、float型(IEEE 754 単精度浮動小数点数)の有効数字は約7桁程度しかありません。5億という大きな値に1を足しても、その差は表現可能な精度を超えてしまうため、結果は丸められて再び「500000000」に戻ってしまいます。その後、「-500000000」を加えると、最終的な結果は「0」になるのです。
一方、2番目の式「(x + y) + z」では、まず「(-500000000 + 500000000) = 0」が計算され、その後に1を加えます。したがって、最終結果は正しく「1」となります。
整数型の場合との比較
同じ計算を int などの整数型で行えば、丸め誤差が発生しないため、計算順序に関係なく必ず同じ結果「1」が得られます。
まとめ
浮動小数点演算では、加算の順序を入れ替えるだけで結果が変わることがあります。これはIEEE 754規格に基づく有限精度表現に起因する、コンピュータ上の数値計算における本質的な特性です。金額計算や高精度が求められる処理では、演算の順序に十分注意するとともに、必要に応じて double 型などより精度の高い型や、多倍長演算ライブラリの活用を検討しましょう。
-
Java 8のStream APIにおける中間操作と終端操作の違い
Java 8で導入されたStream(ストリーム)は、要素を格納するための仕組みではなく、データの集合を効率的に処理するために使用されます。元のコレクション自体は変更されず、パイプラインとして連結されたメソッドに従って結果だけを返します。 Stream APIはさまざまな操作をサポートしており、それらの操作は大きく「中間操作」と「終端操作」の2種類に分類されます。 中間操作(Intermediate Operations)とは 中間操作は、他のメソッドをパイプラインにつなげたり、ストリームを別のストリームへ変換したりするために使用されます。中間操作単体では結果を生成せず、終端操作が実行されるま
-
Pythonのdecimalモジュールを使った正確な10進数・浮動小数点演算の完全ガイド
Pythonにおける浮動小数点数の落とし穴Pythonを含む多くのプログラミング言語では、浮動小数点数はメモリ上で基数2(バイナリ)の分数として表現されます。この仕組みが原因で、浮動小数点演算の結果はときに直感に反するものになります。その典型例が、次のような計算です。>>> 0.1 + 0.2 0.30000000000000004「0.3」になるはずの計算結果に、見慣れない誤差が含まれています。これはPython特有のバグではなく、バイナリ浮動小数点表現の本質的な性質によるものであり、あらゆるプログラミング言語で共通して発生する現象です。decimalモジュールとはこうした