私は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
にそれらを比較したときに問題があったのはなぜこれらの値が異なって出てくるのでしょうか?
浮動小数点加算は、必ずしも結合的である必要はありません。追加する順序を変更すると、結果が変更される可能性があります。
対象の標準紙はWhat Every Computer Scientist Should Know about Floating Point Arithmeticです。次の例を示します。
括弧の解釈には、灰色の領域があります。丸め誤差のために、代数の連想の法則が必ずしも浮動小数点数のために保持されるとは限りません。例えば、x = 1e30、y = -1e30、z = 1のときは、(x + y)+ zはx +(y + z)と全く異なる解を持つ(前者の場合は1、後者の場合は0 )。現在人気のあるマシンとソフトウェアで、そうである何
は、次のとおりです。
コンパイラエンコードされた0.7 0x1.6666666666666p-1(などこれはのパワーに2を乗じた16進数1.6666666666666 -1 )、.2は0x1.999999999999ap-3、.1は0x1.999999999999ap-4となります。これらはそれぞれ、書き込んだ10進数に最も近い浮動小数点で表現できる数値です。
これらの16進浮動小数点定数のそれぞれは、その仮数部に正確に53ビット(「小数部」、しばしば不正確に仮数部と呼ばれます)があることに注意してください。仮数部の16進数は、IEEE-754規格が規定している64ビット2進浮動小数点数の場合、16進数の1と13の16進数(それぞれ4ビット、合計52、 "1"を含む53)ポイント番号。
数字を.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"が奇数なので、 "c"が使用されます。加算の最終結果は0x1.cccccccccccccp-1です。
次に、.1(0x1.999999999999ap-4)の番号を追加します。ここでも、指数が一致するように縮尺を変えているので、0x1.999999999999ap-4は0x.33333333333334p-1になります。それを0x1.ccffcccccccccp-1に加えて、0x1.fffffffffffff4p-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になります。
次に、0x0.66666666666668p-1にスケーリングされた.2(0x1.999999999999ap-3)の番号を追加します。正確な合計は0x2.00000000000008p-1です。浮動小数点の仮数は、常に1から始まるようにスケーリングされます(特殊な場合を除いて、0、無限大、および表現可能な範囲の最下部にある非常に小さい数)ので、これを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)
に等しくありません。
または 'RAND_MAX <100000'の場合は0%! –
例コードは、*連想性*ではなく、*連想性*で異なります。結合性を示すバージョンは '(0.7 +(0.1 + 0.2))'となります。 –
@MattMcNabb:+はバイナリ演算です。浮動小数点オペランドでは、それは可換ではありませんが連想ではありません。したがって、異なる結果をもたらす2つの式がある場合、commutativityのみを適用して、別の結果を生成することはできません。 – tmyklebu
@tmyklebu OKだから、可換性が保持されていることが分かっている場合にのみ、これは連想チェックを行います。 (C++標準はcommutativityを保証するようには見えません)。 –