初めに
色々実験してる時に遭遇しました。
実際、これで嵌るということはなかなかなさそうですが(僕は有りましたが)、
こういうこともあるということで記事にしました。
当初は
「C++で、定数による最適化でsqrt(平方根)の値が異なることがある」としてましたが、定数による最適化のせいではなかったので改めました。
環境
コンパイラは ideoneのC++14 (gcc 5.1)
で確認してます。
実験
# include <iostream>
# include <cmath>
# include <iomanip>
# include <assert.h>
using namespace std;
int main(){
long long int calc = sqrt(static_cast<long double>(18014415152484098LL));
cout << calc <<endl;
long long int calc2 = sqrt(static_cast<double>(18014415152484098LL));
cout << calc2 <<endl;
long long int calc3 = sqrt(static_cast<long long int>(18014415152484098LL));
cout << calc3 <<endl;
long long int calc4 = sqrt(18014415152484098LL);
cout << calc4 <<endl;
cout<<setprecision(20)<< 18014415152484098LL <<endl;
cout<<setprecision(20)<< static_cast<double>(18014415152484098LL) <<endl;
cout<<setprecision(20)<< static_cast<long double>(18014415152484098LL) <<endl;
return 0;
}
これを実行すると、
134217789
134217790
134217790
134217790
18014415152484098
18014415152484096
18014415152484098
と実行される環境があります。
これはsqrt関数でlong doubleに明示的に型指定しないと、doubleになり情報落ちが発生します。
変換後の型 | 値(整数部) |
---|---|
long long int | 18014415152484098 |
long double | 18014415152484098 |
long double sqrt | 134217789 |
double | 18014415152484096 |
double sqrt | 134217790 |
コンパイル時の最適化
また、sqrtの引数が定数とみなせる場合、コンパイラが最適化を行い、
sqrt(18014415152484098) -> 134217790
という感じにコンパイル時に即値にしているようです。
(型にあったsqrt呼出しのようです。)
アセンブラを吐き出してみる
アセンブラを吐くテクニックを教えてもらったので、
実際のアセンブラを吐き出してみるとこのような感じです。
https://ideone.com/4Zoktm
134217790の16進数表現(0x800003e)を
即値で持ってるのがわかります。
(calc2は2箇所で使われているので,2つあるようです)
future study
以下のコードで最初の出力がなぜ
「134217789になる」のかは、まだ理解できてません。
http://ideone.com/Y4XMOY
まとめ
sqrt関数ややこしい。
C++の定数の最適化すごい!