なぜ私が2つの異なる数字を得るのか、誰かに説明してもらえますか? 14および15、次のコードからの出力として?
#include <stdio.h>
int main()
{
double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;
double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;
printf("%d %d",b,c); // 14 15, why?
return 0;
}
両方のケースで15を取得する予定ですが、言語のいくつかの基礎が欠けているようです。
関連するかどうかはわかりませんが、CodeBlocksでテストを行っていました。ただし、一部のオンラインコンパイラで同じコード行を入力すると( この例では1 )、2つの出力変数に対して15の答えが返されます。
これは確かに興味深い質問です。お使いのハードウェアで正確に何が起こるかをここに示します。この答えは、IEEE double
精度の浮動小数点数の精度、つまり、52ビットの仮数と1つの暗黙的なビットの正確な計算を提供します。表現の詳細については、 wikipediaの記事 を参照してください。
では、最初にいくつかの変数を定義します。
double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;
バイナリのそれぞれの値は
Vmax = 10.111001100110011001100110011001100110011001100110011
Vmin = 1.0110011001100110011001100110011001100110011001100110
step = .00011001100110011001100110011001100110011001100110011010
ビットを数えると、設定されている最初のビットに右に52ビットを加えたものが与えられていることがわかります。これは、コンピューターがdouble
を格納する正確な精度です。 step
の値は切り上げられていることに注意してください。
ここで、これらの数値を計算します。最初の操作である減算は、正確な結果をもたらします。
10.111001100110011001100110011001100110011001100110011
- 1.0110011001100110011001100110011001100110011001100110
--------------------------------------------------------
1.1000000000000000000000000000000000000000000000000000
次に、コンパイラによって切り上げられたstep
で除算します。
1.1000000000000000000000000000000000000000000000000000
/ .00011001100110011001100110011001100110011001100110011010
--------------------------------------------------------
1110.1111111111111111111111111111111111111111111111111100001111111111111
step
の丸めにより、結果は15
の下に少しあります。以前とは異なり、notすぐに丸められます。これはまさに興味深いことが起こる場所であるためです。CPUは実際に、 double
なので、丸めはすぐには行われません。
そのため、(Vmax-Vmin)/step
の結果をint
に直接変換すると、CPUは端数ポイントの後のビットを単純にカットします(これは暗黙のdouble -> int
変換が言語標準):
1110.1111111111111111111111111111111111111111111111111100001111111111111
cutoff to int: 1110
ただし、結果を最初にdouble型の変数に格納すると、丸めが行われます。
1110.1111111111111111111111111111111111111111111111111100001111111111111
rounded: 1111.0000000000000000000000000000000000000000000000000
cutoff to int: 1111
そして、これはまさにあなたが得た結果です。
「単純な」答えは、一見単純な数字2.9、1.4、および0.1はすべてバイナリ浮動小数点として内部的に表され、バイナリでは、数値1/10は無限に繰り返されるバイナリ分数0.00011001100110011 ... [ 2] 。 (これは、10進数で1/3が0.333333333 ...になることに似ています。)元の数値は、10進数に変換され、最終的に2.8999999999、1.3999999999、0.0999999999などになります。そして、それらに対して追加の計算を行うと、それらの.0999999999は増殖する傾向があります。
そして、追加の問題は、何かを計算するパスです。特定の型の中間変数に格納するか、「一度に」計算するか、プロセッサが型よりも高い精度で内部レジスタを使用する可能性があることを意味しますdouble
-最終的に大きな違いが生じる可能性があります。
一番下の行は、double
をint
に変換するときに、ほとんど常にroundにしたいということです。切り捨てません。ここで起こったのは、(実際には)1つの計算パスが15に切り捨てた15.0000000001を与え、もう1つが14に切り捨てた14.999999999を与えたことです。
C FAQリスト の question 14.4a も参照してください。
同等の問題が FLT_EVAL_METHOD == 2のCプログラムの分析 で分析されます。
FLT_EVAL_METHOD==2
の場合:
double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;
long double
式を評価してからb
に切り捨てることによりint
を計算しますが、c
の場合はlong double
から評価し、double
に切り捨ててからint
に切り捨てます。
そのため、両方の値が同じプロセスで取得されるわけではありません。また、浮動型では通常の正確な算術演算が提供されないため、結果が異なる場合があります。