2012-01-07 13 views
1

これは、ストリングからダブルへの変換(例:stdlib.hのatof)を行っていたときに発生していました。私は二重変数0.625に "625"という文字列(二重の小数部分を示す)を入れたいと思っていました。奇妙なことは、私が練習の一環としてそれを入れたときに、結果が不正確な結果になったということです。0.62500000000000011または同様のものです。私は、スタンドアロンの方法でそれを置くときしかし、それは次のコードのように、うまく働いた:C++でdouble型の可変乗算が不正確な値になる

int main(int argc, char **argv) { 
string str = "625"; 
double mask = 0.1; 
double frac = 0.0; 
for(int i = 0; i < static_cast<int>(str.length()); ++i) { 
    frac += (str[i] - '0')*mask; 
    mask *= 0.1; 
} 
cout << frac << endl; 

}

は、上記のコードは正確な結果(0.625)を得ました。私が使用し

string PrintDecimal(string input) { 
    long int_part = 0; 
    double frac_part = 0.0; 
    bool is_positive; 
    size_t found; 
    string ret_str; 
    found = input.find('-'); 
    if(found == string::npos) { 
     is_positive = true; 
    } 
    else { 
     is_positive = false; 
     input.erase(found, 1); 
    } 
    found = input.find('.'); 
    if(found == string::npos) { 
     int mask = 1; 
     char app_char; 
     for(int i = static_cast<int>(input.length()-1); i > -1; --i) { 
      int_part += (input[i] - '0')*mask; 
      mask *= 10; 
     } 
     while(int_part != 0) { 
      app_char = (int_part % 2 == 0) ? '0' : '1'; 
      ret_str.push_back(app_char); 
      int_part /= 2; 
     } 
     if(is_positive == false) { 
      ret_str.append("-"); 
     } 
     reverse(ret_str.begin(), ret_str.end()); 
    } 
    else { 
     char app_char; 
     long mask_int = 1; 
     double mask_frac = 0.1; 
     string int_part_str = input.substr(0, found); 
     //string frac_part_str = input.substr(found+1, input.length()-found-1); 
     string frac_part_str = "0."; 
     frac_part_str.append(input.substr(found+1, input.length()-found-1)); 
     for(int i = static_cast<int>(int_part_str.length()-1); i > -1; --i) { 
      int_part += (int_part_str[i] - '0')*mask_int; 
      mask_int *= 10; 
     } 
     //This converting causes 6*0.1 = 0.6000000000009 
     /* 
     for(int i = 0; i < static_cast<int>(frac_part_str.length()); ++i) { 
      frac_part += (frac_part_str[i] - '0')*mask_frac; 
      mask_frac *= 0.1; 
     } 
     */ 
     frac_part = atof(frac_part_str.c_str()); //This works well. 
     while(int_part != 0) { 
      app_char = (int_part % 2 == 0) ? '0' : '1'; 
      ret_str.push_back(app_char); 
      int_part /= 2; 
     } 
     if(is_positive == false) { 
      ret_str.append("-"); 
     } 
     reverse(ret_str.begin(), ret_str.end()); 
     ret_str.push_back('.'); 
     found = ret_str.find('.'); 
     while(frac_part != 0.0) { 
      if(ret_str.length() - found > 64) { 
       cerr << "Can't express accurately." << endl; 
       return "Error"; 
      } 
      frac_part *= 2; 
      if(frac_part >= 1.0) { 
       ret_str.push_back('1'); 
       frac_part -= 1; 
      } 
      else { 
       ret_str.push_back('0'); 
      } 
     } 

    } 
    cout << ret_str << endl; 

    return ret_str; 
} 

コンパイラのバージョンは、gccのバージョン4.2.1(アップル社は5666をビルド)(ドット3)であった。しかし、次のコードは、不正確な結果(0.62500000000000011)を得ました。 問題の原因となるコードのコメント部分に注意してください。私はこの問題の解決策をあなたのアイデアにお願いします。ありがとう!

+5

浮動小数点の素晴らしい世界へようこそ。 –

+1

私はかなり正確に見えます。それはたったの0.00000000000000011です! –

+0

これで、ほとんど誰もがリードで浮動小数点で噛まれると思います。私が分数なしで1/3を書くように言ったら、0.3333と書いています...そして、ある時点で、やめて(十分であれば)言う必要があります。今、エクササイズとして、1/10(0.1)でもバイナリで同じ操作をしてください。 –

答えて

2

を使用してみてくださいより精度の結果を出し、私たちは完全な精度に中間結果を印刷し、

0.62500000000000011102 

取得し、

Prelude Text.FShow.RealFloat> mapM_ print $ scanl (+) (FD 0) $ zipWith (*) (iterate (*0.1) 0.1) [6,2,5] 
0.0 
0.600000000000000088817841970012523233890533447265625 
0.62000000000000010658141036401502788066864013671875 
0.62500000000000011102230246251565404236316680908203125 

最も正確な結果を得るには、より複雑なアルゴリズムを使用する必要があります。たとえば、文字列を有理数として解析し、それを変換する必要があります。

迅速な部分的な解決策はnumerator/(10^k)を得た小数部を解析することで、(非負指数を有する)10の

double denominator = 1.0; 
uint64_t numerator = 0; 
for(i = f0; i < len; ++i) { // f0 index of first digit after decimal point 
    numerator = 10*numerator + (str[i] - '0'); 
    denominator *= 10; 
} 
double fractional_part = numerator/denominator; 

電力は、指数< = 22(正確にしばらくdoubleのように表すことができます。分数部分が長すぎない場合、64ビットIEEE754 doubleを仮定して)、分子も正確に表すことができます。次に、必要な丸め、最終的な除算、および結果が正確な数学的結果に最も近い表現可能な数であるために不正確な結果が発生する点が1つだけあります。 (不完全さの別の点は、整数部に小数部を追加することです)。

上記は、あまりにも大きい積分部分と十分な小数部分では入力のための良い結果を生成しますが、それは非常に間違っています長い分数部分。

浮動小数点数の正確な解析と表示は、複雑なビジネスであるです。

+0

しかし、例えば0.625をとると、バイナリ形式が無限長である小数部分ではありません...バイナリで0.101で表すことができます。それの後にそのような小さな尾を追加する理由はありません。有限の方法で(バイナリ形式で)表現することができるので、なぜこのようになるのでしょうか? –

+0

これはアルゴリズムの結果です。 0.1は正確に表現することができないため、0.1を掛けると近似誤差が生じ、それらが累積します。 –

+0

どうもありがとう! –

0

あなたは出力フロートのことができるようにするために精度を設定する必要があり、お好みの精度まで倍増:
は、我々は印刷する場合、実際に最初は、どちらか正確ではありませんが、まあsetprecision().

+0

解決策の1つを提供してくれてありがとうございましたが、問題の原因を理解できません。参照:atof()はうまくいく、それにはどんな魔法があるの? –

+0

すべては、使用されている内部浮動小数点型と実際に表示しようとしている数によって異なります。別のコンパイラを使用するか、場合によっては別のプロセッサを使用しても、少し異なる結果が得られます。ルール1は、2つの浮動小数点を直接比較することは決してありません。一般人の言葉では正確ではありませんが、近似値です。 –

0

doubleはバイナリ浮動小数点表現です。

これは、の十進数の番号を正しくモデル化できないことを意味します。

これが意味をなさない場合は、thisをお読みください。

小数点をモデル化する場合は、10進浮動小数点表現を試してみるとよいでしょう。

gcc 4.6.1のようにC++ 11コンパイラにアクセスできる場合は、std :: decimal :: decimal64を使用して小数点以下を正確に表現できます。そうでない場合はthis libを使用できます。

+0

わかりましたが、私は正確にバイナリで表すことができない小数点0.1に気付きませんでした。とにかくありがとう! –

関連する問題