読者です 読者をやめる 読者になる 読者になる

doubleの入出力で誤差を生みたくない話

 double型の値をtxtなどに保存して、次に読みだしたときに完全に元の値に戻す方法を備忘録がてら話します。
 何も考えずにdouble型を標準出力すると、

double x = 0.123456789;
cout << x << endl; //-> 0.123457

となってしまい、勝手に四捨五入されてしまっています。この問題は、出力の桁数を設定することなどによりある程度回避することが可能ですが、やはりどこかで誤差が生まれてしまい、標準出力した後に標準入力すると元の値と変わってしまいます。そのようなことを呟いていると


とリプを飛ばしてもらいました。確かに、double型とlong long int型のサイズは64bitなのでうまくlong long intで表現できそうです。
 しかし、ただ単にdoubleをlong long intにキャストすると以下のように小数点以下が落とされてしまいます。

double x = 0.123;
cout << (long long int)x << endl; //->0

 そこで、共用体というものを使います*1。共用体とは、構造体のようなものですが、メンバ変数が同じメモリ上に配置されます。これが何を意味するかというと、同じメモリ上をchar型で読んだりint型で読んだりと色々な型で読み書きできるということです。これを使えばdouble型をlong long int型に変換できそうです。
実際に以下のように書いてみると、うまくいっているのがわかりました。

union{
  double x;
  long long int y;
} uni;

uni.x = 0.123456789;
cout << uni.y << endl; //-> 4593560419846153055
union{
  double x;
  long long int y;
} uni;

cin >> uni.y; //<- 4593560419846153055
printf("%.10f\n", uni.x); //-> 0.1234567890

追記

 ポテチプロからそれscanfとかでもできるよとのツッコミをいただきました。実際にやってみると共用体など使わなくても以下のように書けばできました。

long long x;
scanf("%lf", &x); //<- 0.123456789
cout << x << endl; //-> 4593560419846153055
double y = 0.123456789;
printf("%lld\n", y); //-> 4593560419846153055

型検査なんてものはないんですねという気持ちになりましたがまぁこれがC言語なのでしょう。こういう関数がどうやって動いているのかとかもさっぱりなので勉強不足ですね、はい。

追追記

 クッキープロからそれprintfだと怪しいよとのツッコミをいただきました。scanfできるならprintfもできるでしょと僕が勝手に付け加えたのですがそうではないようです。x64だとレジスタがうんちゃらという話だそうですが、僕はそういうところがさっぱりなのでこの記事はこの辺で切っておきます... 
 

C言語は怖い言語です。

*1:僕もこれまでは名前を聞いたことがあるだけで使ったことがありませんでした