2016-12-30 3 views
0

floor異なりの行動と、なぜ私は理解していない。このコードの出力があるこの場合、関数の床が異なる結果をもたらすのはなぜですか?この例では

printf("floor(34000000.535 * 100 + 0.5) : %lf \n", floor(34000000.535 * 100 + 0.5)); 
printf("floor(33000000.535 * 100 + 0.5) : %lf \n", floor(33000000.535 * 100 + 0.5)); 

floor(34000000.535 * 100 + 0.5) : 3400000053.000000 
floor(33000000.535 * 100 + 0.5) : 3300000054.000000 

なぜ最初の結果は、我々として3400000054.0に等しくありません。期待できる?

+1

正確な計算が必要な場合は、丸めの問題はもちろん、任意精度の計算(たとえばgmp)のライブラリを使用してください。 – Ctx

+2

あなたはバイナリ浮動小数点を扱っているからです。 – cHao

+0

@cHao _decimal_ [浮動小数点](https://en.wikipedia.org/wiki/Decimal64_floating-point_format)でも、このような問題は、書式の精度限界に近い値で発生することに注意してください。これは浮動小数点演算のプロパティで、どのベースでも同じです。間違いなく10進数のテキストとバイナリの 'double'でよく使われます。 – chux

答えて

3

doubleは、テキストで表現できるすべての可能な数を表しているわけではありません。

doubleは、典型的には、約2 の異なる数字を表すことができる。 doublebinary floating point番号としてエンコードされている場合は、34000000.53533000000.535も設定されていません。代わりに、最も近い表現可能な番号が使用されます。バイナリ浮動小数点数としてdouble

Text    34000000.535 
closest double 34000000.534999996423... 
Text    33000000.535 
closest double 33000000.535000000149... 

、100.0のように、非パワーの-2を乗じ、追加の丸め差異を導入することができます。しかし、これらのケースでは、依然としてxxx.5のすぐ上にある製品と、それよりも下にある製品が生成されます。

0.5を追加すると、単純な2の累乗で、値が3x00000053.5に比べて極端ではないため、丸めの問題は発生しません。

中間結果を高い印刷精度に見れば、典型的なステップバイステップのプロセスがよく示されます。

#include <stdio.h> 
#include <float.h> 
#include <math.h> 

void fma_test(double a, double b, double c) { 
    int n = DBL_DIG + 3; 
    printf("a b c  %.*e %.*e %.*e\n", n, a, n, b, n, c); 
    printf("a*b  %.*e\n", n, a*b); 
    printf("a*b+c  %.*e\n", n, a*b+c); 
    printf("a*b+c  %.*e\n", n, floor(a*b+c)); 
    puts(""); 
} 

int main(void) { 
    fma_test(34000000.535, 100, 0.5); 
    fma_test(33000000.535, 100, 0.5); 
} 

様々なプラットフォームが1)小数点浮動小数点doubleを使用し、まれlong doubleまたは2)のような高精度の数学を使用することができるように、出力

a b c  3.400000053499999642e+07 1.000000000000000000e+02 5.000000000000000000e-01 
a*b  3.400000053499999523e+09 
a*b+c  3.400000053999999523e+09 
a*b+c  3.400000053000000000e+09 

a b c  3.300000053500000015e+07 1.000000000000000000e+02 5.000000000000000000e-01 
a*b  3.300000053500000000e+09 
a*b+c  3.300000054000000000e+09 
a*b+c  3.300000054000000000e+09 

は、問題は、この簡単な答えより複雑です。したがって、コードの結果は異なる場合があります。 code that shows the representation of floats in memory as sum of termsを使用して

+0

chux、2^64の異なる数字があると言って間違いました。実際には、より多くのものが少なくなります。それらの64の中に指数部があるので、私が答えて分析した 'float'の場合と同じ種類の表現です。コンパイラは、数値100を10e2または100e1として表すことができます。指数部には、コンパイラーがどのように設定するべきかについての「プロパティー」はありません。 'double' /' long double'/'float'がどのように表現されているかを表示する私の答えを見てください。したがって、x = 100と書くと、コンパイラが与える表現や浮動小数点ハードウェアのどちらも確信できません。 – alinsoar

+0

@alinsoarこの答えは、2^64の異なる数はないと言っています。あなたの不要な編集の前に、この答えは「約2^64の異なる数字」と言いました。実際には、**より多くの少ない値がありません。 2^64の異なるビットパターンのチャンクは、0.1%未満の値パターン・カウントしか減少させないNot-A-Numbersです。あなたの指数を10e2または100e1として印刷することについての議論は、依然として2^64パターンの数の1つに過ぎません。この回答を編集しないでください。編集とコメントの意図が正しい意図を誤ってしまいます。 – chux

+0

「約」よりも正しいので、「より小さい」を使用できました。 – alinsoar

0

質問はすでに回答済みですhere

基本フロートの数値は近似値です。私たちはこのようなプログラムを持っている場合:

float a = 0.2 + 0.3; 
float b = 0.25 + 0.25; 

if (a == b) { 
    //might happen 
} 
if (a != b) { 
    // also might happen 
} 

唯一の保証の事はa-bが比較的小さいことです。

-1

は、我々が得る:

main() 
{ 
    float x=floor(34000000.535 * 100 + 0.5); 
    float y=floor(33000000.535 * 100 + 0.5); 
    xx(&x); 
    xx(&y); 
    yy(x); 
    yy(y); 
} 

このコードが出力されますどちらの場合も、床によって返される値のメモリで表現。

calcultorを使用すると、近似は実際には良好であることがわかりますが、床表現の背後にある数学のためにいくつかの摂動があります。

注:bcscale=20を設定しました。つまり、各中間計算はそのポイントの後20桁を保持します。

./a.out 
1ST NUMBER=> sign:0 exponent:1 0 0 1 1 1 1 fraction:0 1 0 0 1 0 1 0 1 0 1 0 0 1 1 1 1 1 1 0 0 0 1 0 
2ND NUMBER=> sign:0 exponent:1 0 0 1 1 1 1 fraction:0 1 0 0 0 1 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0 0 1 
1ST NUMBER=> positive (1+1/(2) +1/(16) +1/(64) +1/(256) +1/(1024) +1/(8192) +1/(16384) +1/(32768) +1/(65536) +1/(131072) +1/(262144) +1/(4194304))*2^31 
2ND NUMBER=> positive (1+1/(2) +1/(32) +1/(256) +1/(1024) +1/(2048) +1/(16384) +1/(8388608))*2^31 
@ bc 
scale=20 
(1+1/(2) +1/(16) +1/(64) +1/(256) +1/(1024) +1/(8192) +1/(16384) +1/(32768) +1/(65536) +1/(131072) +1/(262144) +1/(4194304))*2^31 
3399999999.99999999999463129088 
(1+1/(2) +1/(32) +1/(256) +1/(1024) +1/(2048) +1/(16384) +1/(8388608))*2^31 
3299999999.99999999999731564544 
+0

あなたはここで何かを混ぜていると思います。元の質問には、問題の根本原因である2つの異なる数字(34 ... vs 33 ...)がありました。あなたは 'floor()'を適用したときに同じ結果を与える同じ番号を2回持っています。ここでprintfの実装は考慮されていません。 – Ctx

+0

@Ctxありがとうございます。私は数字が違っていることに気付かなかった。修正されました。 – alinsoar

+0

'void main'は未定義の動作を呼び出します。 –

関連する問題