3つの浮動小数点値を追加し、それらを1と比較しているときに、問題が発生しました。
cout << ((0.7 + 0.2 + 0.1)==1)<<endl; //output is 0
cout << ((0.7 + 0.1 + 0.2)==1)<<endl; //output is 1
なぜこれらの値が異なるのでしょうか?
3つの浮動小数点値を追加し、それらを1と比較しているときに、問題が発生しました。
cout << ((0.7 + 0.2 + 0.1)==1)<<endl; //output is 0
cout << ((0.7 + 0.1 + 0.2)==1)<<endl; //output is 1
なぜこれらの値が異なるのでしょうか?
浮動小数点の加算は必ずしも結合的ではありません。合計する順序を変更すると、結果が変わる可能性があります。
この主題に関する標準的な論文は、すべてのコンピューター科学者が浮動小数点演算について知っておくべきことです。次の例を示します。
もう1つの灰色の領域は、括弧の解釈に関するものです。丸め誤差のため、代数の結合法則は必ずしも浮動小数点数に当てはまるとは限りません。たとえば、式(x + y)+ zは、x = 1e30、y = -1e30、z = 1の場合のx+(y + z)とはまったく異なる答えになります(前者の場合は1、後者の場合は0です)。 )。
現在人気のあるマシンとソフトウェアでは、次のようになります。
コンパイラ.7
は、0x1.6666666666666p-1(16進数の1.6666666666666に2を-1の累乗で乗算したもの)、.2
0x1.999999999999ap-3、および.1
0x1.999999999999ap-4としてエンコードされます。これらのそれぞれは、あなたが書いた10進数に最も近い浮動小数点で表現可能な数です。
これらの16進浮動小数点定数のそれぞれが、その仮数に正確に53ビットを持っていることに注意してください(「小数」部分、しばしば不正確に仮数と呼ばれます)。仮数の16進数には、「1」とさらに13桁の16進数(それぞれ4ビット、合計52、「1」を含む53)があります。これは、IEEE-754標準が64ビットのバイナリ浮動小数点に対して提供するものです。ポイント番号。
.7
との数字を足してみましょう.2
:0x1.6666666666666p-1および0x1.999999999999ap-3。まず、最初の数値と一致するように2番目の数値の指数をスケーリングします。これを行うには、指数に4を掛け(「p-3」を「p-1」に変更)、仮数に1/4を掛けて、0x0.66666666666668p-1を求めます。次に、0x1.6666666666666p-1と0x0.66666666666668p-1を追加して、0x1.ccccccccccccc8p-1を作成します。この数値の仮数は53ビットを超えていることに注意してください。「8」はピリオドの後の14桁目です。浮動小数点はこれだけのビット数の結果を返すことができないため、表現可能な最も近い数値に丸める必要があります。この場合、0x1.cccccccccccccp-1と0x1.ccccccccccccdp-1の2つの同じように近い数値があります。同点の場合、仮数の最下位ビットがゼロの数値が使用されます。「c」は偶数で「d」は奇数なので、「
次に、それに.1
(0x1.999999999999ap-4)の番号を追加します。ここでも、指数が一致するようにスケーリングするため、0x1.999999999999ap-4は0x.33333333333334p-1になります。次に、それを0x1.cccccccccccccp-1に追加して、0x1.ffffffffffffff4p-1を指定します。これを53ビットに丸めると、0x1.fffffffffffffp-1が得られ、これがの最終結果になり.7+.2+.1
ます。
ここで考えてみましょう.7+.1+.2
。には.7+.1
、0x1.6666666666666p-1と0x1.999999999999ap-4を追加します。後者は0x.33333333333334p-1にスケーリングされていることを思い出してください。その場合、正確な合計は0x1.99999999999994p-1です。これを53ビットに丸めると、0x1.9999999999999p-1になります。
次に、.2
(0x1.999999999999ap-3)の数値を追加します。これは、0x0.66666666666668p-1にスケーリングされます。正確な合計は0x2.00000000000008p-1です。浮動小数点の仮数は常に1から始まるようにスケーリングされるため(特殊な場合:ゼロ、無限大、および表現可能な範囲の下部にある非常に小さい数値を除く)、これを0x1.00000000000004p0に調整します。最後に、53ビットに丸めて、0x1.0000000000000p0を指定します。
したがって、丸め時に発生するエラーのため、.7+.2+.1
0x1.fffffffffffffp-1(1よりわずかに小さい)を.7+.1+.2
返し、0x1.0000000000000p0(正確には1)を返します。
浮動小数点の乗算は、CまたはC++では結合法則ではありません。
証拠:
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
using namespace std;
int main() {
int counter = 0;
srand(time(NULL));
while(counter++ < 10){
float a = rand() / 100000;
float b = rand() / 100000;
float c = rand() / 100000;
if (a*(b*c) != (a*b)*c){
printf("Not equal\n");
}
}
printf("DONE");
return 0;
}
このプログラムでは、時間の約30%はに(a*b)*c
等しくありませんa*(b*c)
。
加算も乗算も、IEEE 743倍精度(64ビット)数値と関連していません。それぞれの例を次に示します(Python 3.9.7で評価)。
>>> (.1 + .2) + .3
0.6000000000000001
>>> .1 + (.2 + .3)
0.6
>>> (.1 * .2) * .3
0.006000000000000001
>>> .1 * (.2 * .3)
0.006
エリックと同様の答えですが、追加用で、Pythonを使用しています。
import random
random.seed(0)
n = 1000
a = [random.random() for i in range(n)]
b = [random.random() for i in range(n)]
c = [random.random() for i in range(n)]
sum(1 if (a[i] + b[i]) + c[i] != a[i] + (b[i] + c[i]) else 0 for i in range(n))