web-dev-qa-db-ja.com

Cのint変数への倍精度数の割り当ての直感的でない結果

なぜ私が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の答えが返されます。

47
GeorgiD

これは確かに興味深い質問です。お使いのハードウェアで正確に何が起こるかをここに示します。この答えは、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

そして、これはまさにあなたが得た結果です。

24
cmaster

「単純な」答えは、一見単純な数字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-最終的に大きな違いが生じる可能性があります。

一番下の行は、doubleintに変換するときに、ほとんど常にroundにしたいということです。切り捨てません。ここで起こったのは、(実際には)1つの計算パスが15に切り捨てた15.0000000001を与え、もう1つが14に切り捨てた14.999999999を与えたことです。

C FAQリストquestion 14.4a も参照してください。

21
Steve Summit

同等の問題が 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に切り捨てます。

そのため、両方の値が同じプロセスで取得されるわけではありません。また、浮動型では通常の正確な算術演算が提供されないため、結果が異なる場合があります。