none
floatの型変換で数字が化ける? RRS feed

  • 質問

  • longとfloatの型を変換すると、ビット化けするような現象があり、困っております。

    解消方法があればご教示ください。

     

    プログラム(イメージ)

    {

    float*  fdata;

    long*  lvalue;

     

    *float = 367.070282;

    *((float*)lvalue) = *float;

    }

    というコーディングで実行すると、*((float*)lValueの値は367.570282になってしまいます。

     

    つまり、=で代入する

    入力データ367.070282は0x43b788ff(ビックエンディアン)

    出力データ367.570282は0x43bc88ff(ビックエンディアン)

    です。他の値でも試してみると、下位8ビットが1のときに下から15ビット目が0->1に変化してしまうようです。

     

    memcopyで回避はできるようですが、できればコーディングを変えることなく正しく動作するようにするのが希望です。

     

    よろしくお願いいたします。

     

    <実施環境>

    Windows XP  SP2

    Visual Studio 2005  SP1

    2008年7月22日 20:53

回答

  •  hhst さんからの引用
    実際はもう少し複雑なコードだったので、簡略化して投稿しました。


    こういう微妙な問題は、再現性のある実際のコードを提示しないと話が先に進んでいかないと思います。

     hhst さんからの引用
    下記は元プログラムを使って、他にトライした結果です。

    ・Visual Studio 2003では再現せず

    ・Linux(Red Hat)では再現せず

    ・リトルエンディアンで処理すると再現せず

    ・ビッグエンディアンで処理すると再現



    「リトルエンディアンで処理」とか「ビッグエンディアンで処理」という表現も少々曖昧です。
    リトルエンディアンやビッグエンディアンのマシン上でプログラムを走らせるということでしょうか。
    それとも、何らかのデータがあって、それをリトルエンディアンやビッグエンディアンのデータと見なして処理をするということでしょうか。

    それから、

     hhst さんからの引用
    つまり、=で代入する

    入力データ367.070282は0x43b788ff(ビックエンディアン)

    出力データ367.570282は0x43bc88ff(ビックエンディアン)

    です。他の値でも試してみると、下位8ビットが1のときに下から15ビット目が0->1に変化してしまうようです。


    とありますが、

    入力: 0x43b788ff

    出力: 0x43bc88ff

    を2進数で表現すると、

    入力: 0100 0011 1011 0111 1000 1000 1111 1111

    出力: 0100 0011 1011 1100 1000 1000 1111 1111

    となります。


    「下位8ビットが1のときに」は「下位8ビットが全て1のときに」ということだと思いますが、

    「15ビット目が0->1に変化してしまう」の部分が良く分かりません。

    このデータをリトルエンディアンに並べ直し、「15ビット目」を「下から 0 based で数えて15ビット目」と解釈すれば 確かにその部分は 0 から 1 に変化していますが、変化している部分はそれだけではありません。


    ここから先は予想ですが、VS2003 と VS2005 で signed, unsigned に対するシフト演算や桁あふれの処理に変化があったのではないでしょうか。

    もし、省略したコードにそういった処理が含まれているなら、その部分を重点的にデバッグしてみると何か見つかるかも知れません。



    2008年7月28日 1:35
  • こんにちは。少々興味を持ちまして、.net2003 VC++でアセンブルソースを吐き出してみましたら

     

    ; Line 21    ..。。 *pf = 367.070282f;
     mov eax, DWORD PTR _pf$[ebp]
     mov DWORD PTR [eax], 1136101631  ; 43b788ffH

    ; Line 22     ..。。 *((float*)pl) = *pf;
     mov eax, DWORD PTR _pl$[ebp]
     mov ecx, DWORD PTR _pf$[ebp]
     mov edx, DWORD PTR [ecx]
     mov DWORD PTR [eax], edx

     

    どうも見たところ、4バイトDWORD のmemcpyで、PTR [eax]へコピーされているようです。

    ということは、変化のしようがないはずで・・・

     

    問題とされている2005では、この部分で何らかの違いがあるのでしょうが、

    long に変換?されたら小数は無くなるから、ありえないと思いますよ

    それより、union とかでバイトをいじくっているとか、

    最適化で思わぬ同時実行が起こされてるとか、(コンパイルの指定で最適化なしでテストをしてみては)

    いかがでしょう・・・
    2008年7月28日 5:13

すべての返信

  • 再現できません。
    XP(32bit) + 2005 で以下のコードを試しましたが、f も l も 0x43b788ff になりました。

    float f;
    float* pf = &f;
    long l;
    long* pl = &l;
    *pf = 367.070282f;
    *((float*)pl) = *pf;

    2008年7月23日 0:34
  • 回答ありがとうございました。

     

    記載いただいたコードでは確かに再現しませんでした。

     

    実際はもう少し複雑なコードだったので、簡略化して投稿しました。

    下記は元プログラムを使って、他にトライした結果です。

    ・Visual Studio 2003では再現せず

    ・Linux(Red Hat)では再現せず

    ・リトルエンディアンで処理すると再現せず

    ・ビッグエンディアンで処理すると再現

    2008年7月27日 21:04
  •  hhst さんからの引用
    実際はもう少し複雑なコードだったので、簡略化して投稿しました。


    こういう微妙な問題は、再現性のある実際のコードを提示しないと話が先に進んでいかないと思います。

     hhst さんからの引用
    下記は元プログラムを使って、他にトライした結果です。

    ・Visual Studio 2003では再現せず

    ・Linux(Red Hat)では再現せず

    ・リトルエンディアンで処理すると再現せず

    ・ビッグエンディアンで処理すると再現



    「リトルエンディアンで処理」とか「ビッグエンディアンで処理」という表現も少々曖昧です。
    リトルエンディアンやビッグエンディアンのマシン上でプログラムを走らせるということでしょうか。
    それとも、何らかのデータがあって、それをリトルエンディアンやビッグエンディアンのデータと見なして処理をするということでしょうか。

    それから、

     hhst さんからの引用
    つまり、=で代入する

    入力データ367.070282は0x43b788ff(ビックエンディアン)

    出力データ367.570282は0x43bc88ff(ビックエンディアン)

    です。他の値でも試してみると、下位8ビットが1のときに下から15ビット目が0->1に変化してしまうようです。


    とありますが、

    入力: 0x43b788ff

    出力: 0x43bc88ff

    を2進数で表現すると、

    入力: 0100 0011 1011 0111 1000 1000 1111 1111

    出力: 0100 0011 1011 1100 1000 1000 1111 1111

    となります。


    「下位8ビットが1のときに」は「下位8ビットが全て1のときに」ということだと思いますが、

    「15ビット目が0->1に変化してしまう」の部分が良く分かりません。

    このデータをリトルエンディアンに並べ直し、「15ビット目」を「下から 0 based で数えて15ビット目」と解釈すれば 確かにその部分は 0 から 1 に変化していますが、変化している部分はそれだけではありません。


    ここから先は予想ですが、VS2003 と VS2005 で signed, unsigned に対するシフト演算や桁あふれの処理に変化があったのではないでしょうか。

    もし、省略したコードにそういった処理が含まれているなら、その部分を重点的にデバッグしてみると何か見つかるかも知れません。



    2008年7月28日 1:35
  • こんにちは。少々興味を持ちまして、.net2003 VC++でアセンブルソースを吐き出してみましたら

     

    ; Line 21    ..。。 *pf = 367.070282f;
     mov eax, DWORD PTR _pf$[ebp]
     mov DWORD PTR [eax], 1136101631  ; 43b788ffH

    ; Line 22     ..。。 *((float*)pl) = *pf;
     mov eax, DWORD PTR _pl$[ebp]
     mov ecx, DWORD PTR _pf$[ebp]
     mov edx, DWORD PTR [ecx]
     mov DWORD PTR [eax], edx

     

    どうも見たところ、4バイトDWORD のmemcpyで、PTR [eax]へコピーされているようです。

    ということは、変化のしようがないはずで・・・

     

    問題とされている2005では、この部分で何らかの違いがあるのでしょうが、

    long に変換?されたら小数は無くなるから、ありえないと思いますよ

    それより、union とかでバイトをいじくっているとか、

    最適化で思わぬ同時実行が起こされてるとか、(コンパイルの指定で最適化なしでテストをしてみては)

    いかがでしょう・・・
    2008年7月28日 5:13
  • こんにちは。中川俊輔 です。

     

    zakioさん、y4yamaさん、回答ありがとうございます。

     

    hhstさん、フォーラムのご利用ありがとうございます。

    その後いかがでしょうか?

    追加の質問等ありましたら、ぜひまた投稿してみてください!

    有用な情報と思われたため、zakioさん、y4yamaさんの回答へ回答済みチェックをつけさせていただきました。

     

    回答済みチェックが付くことにより、有用な情報を探している方が情報を見つけやすくなります。
    有用な情報と思われる回答があった場合は、なるべく回答済みボタンを押してチェックを付けてください。

    hhstさんはチェックを解除することもできますので、ご確認ください。

     

    それでは!

    2008年8月11日 8:08
  • 以下のコードで再現できました。

    float f1 = 367.070282f;
    unsigned long l1 = *((unsigned long*)(&f1));

    unsigned long swapped =
        ((l1 << 24) & 0xff000000) |
        ((l1 <<  8) & 0x00ff0000) |
        ((l1 >>  8) & 0x0000ff00) |
        ((l1 >> 24) & 0x000000ff);

    float f2 = *((float*)(&swapped));
    unsigned long l2 = *((unsigned long*)(&f2));

    これで l2 と swapped が別の値になります。

    多分、このコードのように、どこかで float として扱っている部分があるのでしょう。
    0x43b788ff を反転させて得られる 0xff88b743 は float 的には NaN なので、
    f2 に代入する際に NaN の内部表現が変わったのではないかと思われます。

    2008年8月13日 7:30
  • 私も同じような現象に会いましたのでプログラム(バグが出る範囲で規模縮小&改変)を投稿させていただきます。

    #include <math.h>
    #define 位置1x 583
    #define 位置1y 368
    #define 位置2x 607
    #define 位置2y 491
    long Round(float 浮動小数点数){//自作の関数
    	float i;
    	long j;
    	j = (long)浮動小数点数;
    	i = 浮動小数点数 - j;
    	if(i>0.5){
    		j++;
    	}
    	return j;
    }
    int main(){
    	static long x位置 = 0, y位置 = 0;
    	static float xy合成距離;
    	static short x軸距離, y軸距離;
    	static unsigned long 時間 = 1;
    	while(1){
    		x軸距離=位置1x-位置2x;
    		y軸距離=位置1y-位置2y;
    		xy合成距離 = sqrt((float)(x軸距離*x軸距離+y軸距離*y軸距離));
    		x位置 = Round(位置2x+時間*x軸距離/xy合成距離);
    		y位置 = Round(位置2y+時間*y軸距離/xy合成距離);
    		時間++;
    	}
    	return 0;
    }
    

    以上のプログラムをデバッグすると、自作関数の最初の引数が"浮動小数点数 34272720. float"となります。
    もしかしたら、自作関数が悪いのでしょうか?
    ちなみに、実際のプログラムはWin32アプリケーションです。

    環境
    Windows Vista
    Visual C++ 2010(設定は変えていません。)

    初心者&初投稿ですがよろしくお願いします。

    • 編集済み ka_jpn 2011年12月25日 13:20 見映え修正
    2011年12月25日 6:55
  • Roundの引数でunsigned long とshortの負数を計算しているからです。
    計算ではshortがunsigned longに拡張されますが、unsignedでは負数を扱えないのでおかしくなるだけです。
    正しく計算するにはfloatにキャストするなりして負数を扱える型どうしで計算をする必要があります。

    型の表現できる値を意識して計算するようにしましょう。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
    2011年12月25日 10:35
  • なるほど、Roundの引数のところでfloat型があるので、全部float型になってから計算されると誤解していました。

    調べてみたら乗除算は左にあるものから順番に(型も数値も)評価されるんですね。型だけは最初に一斉に評価されると思っていました。

    結果的にはfloatですが、先にunsigned longとsigned shortが評価され、バグった"unsigned long"になると。(←ここでエラーとか警告にならないのですね。)

    ここのステップを知りませんでした。

     

    話題と違うところでお騒がせしてすいませんでした。

    2011年12月25日 13:09
  • 算術二項演算子間で数値型が違う場合にどのように型変換されるかがUsual Arithmetic Conversionsに示されています。
    2011年12月25日 13:26