printf("%d", f)でfがfloatの時の挙動について

型はバイト幅だけじゃなくてどうやって二進法でエンコードするのかも規定するよね、という話をしていて確認のためのコードを書いた。手抜きをして方法Bでやってもいいよね、と思ったら予想外の結果が出たので悩んでいる。→解決

#include<stdio.h>
int main(){
  int i;
  float f = 1.0;
  /* 素直な方法 A (追記:やっちゃダメ!)*/
  i = *(int*)(&f);
  printf("A %d\n", i);
  /* 手抜きな方法 B */
  printf("B %d\n", f);
}

Rubyでこんな感じに文字列化して、と。

irb> def pp(x) x.to_s(2).rjust(32, "0") end
irb> pp 1065353216
=> "00111111100000000000000000000000"
0.500000の時
A 1056964608 : 00111111000000000000000000000000
B 8388608    : 00000000100000000000000000000000

1.000000の時
A 1065353216 : 00111111100000000000000000000000
B 8388608    : 00000000100000000000000000000000

2.000000の時
A 1073741824 : 01000000000000000000000000000000
B 8388608    : 00000000100000000000000000000000

なんでBの方は値が変わらないのだろう?まあ方法Aを使えば問題ないんだけど、理由がわからないのはすっきりしないな。



怒られる前に追記

$ uname -a
Darwin nishio.local 10.7.0 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386

$ gcc -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5659~1/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5659)



解決編

nyaxt: 西尾さんのぶろぐよんだ。プラットフォームがx64じゃないかというえすぱー。
nyaxt: ABIじゃないかなーという。x86だと全部スタックにつむけど、x64だとレジスタ渡しするので浮動小数点数mmxレジスタにはいるんじゃね(追記:以下s/MMX/SSE/ig)

nyaxt: やっぱりそうだw 手元のこーどみたらやっぱりBだとxmm0にmovssしてる

nishio_: MMXレジスタに入れて、そこからとってもらえるつもりで呼んだのに、printfの側が「%dだからスタックから取ろう」って取って変な値を返しているってこと?
nyaxt: スタックからとってるんじゃなくてレジスタからとってるはず
nishio_: 浮動小数点を渡そうと思ってMMXレジスタに入れたけど、printfは別のレジスタから取っちゃって値が変になる、ってことか
nyaxt: そうそう

nyaxt: これを応用するとこういう手品ができる


#include
int main(){
printf("A: %d %f %d %f \n", 1, 2.0, 3, 4.0);
printf("A: %d %f %d %f \n", 1, 3, 2.0, 4.0);
}
nishio_: 面白い
nyaxt: 元ネタはshinhさんなので http://shinh.skr.jp/slide/tcc64/000.html
nishio_: 言及しとく



などと書いていたら解決編2

@herumi: いや,それ素直な方法も手抜きな方法も未定義なコードですから何が起こっても文句言えないです.gccならC99の%aを使うのも手. http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/printf.3.html

@gusmachine: unionを使うのが正解、でしょうか? http://bit.ly/fUn8wb

#include<stdio.h>
union intfloat{
  int i;
  float f;
};

int main(){
  intfloat x;
  x.f = 1.0;
  printf("%f\n", x.f);
  printf("int %d\n", x.i); //-> 1065353216
}

これが正解ですね。



@kosaki55tea 一般にはxmmはMMXレジスタとは言わない気がするのですが僕の周囲だけ?mm0とかがMMXレジスタでxmm0とかはSSEレジスタのような

たしかにここでもSSEレジスタと書かれてますね: about x86 CPU