2010年9月3日金曜日

日記ちゃん

 エンディアンの変更はとりたてて特殊なことはせず普通に上下ひっくり返した。
汎用的なのが1個あれば十分かなあ、データ部分での速度を考えると2Byte用があってもいいかもしれない。

void changeEndianness( void* dest, size_t size )
{
  BYTE* destByte = static_cast<BYTE*>( dest );
  const size_t n = size/2;
  for (size_t i=0; i<n; ++i)
  {
    BYTE tmp = destByte[i];
    destByte[i] = destByte[size-i-1];
    destByte[size-i-1] = tmp;
  }
}

 
 問題は80bit浮動小数点数のほうだけど、
まずbitを操作して正しく値がちゃんと取れるかどうか検証すべく、
floatをdoubleにするプログラムを試作。キャストでなしにね。げた脱がせたり履かせたり。
結果うまくうごいた。理屈の上では問題ないらしい。
長いので乗せないが凄く環境依存コードです……。まあいまさらだけど。
 あとは書くだけか。
 
 テストにしてもなんでdoubleをfloatにするプログラムにしなかったんだろう……。
逆じゃね普通。
 
 そんなわけでdoubleをfloatに落とすプログラムを書いてテストした。

#include <cstdio>
#include <cstring>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
 
void print16( void* data, int size )
{
  BYTE* bin = static_cast<BYTE*>( data );
  std::printf("0x");
  for (int i=size-1; i>=0; --i) std::printf("%02x", bin[i]);
  std::printf("\n");
}
 
 
float doubleToFloat( double val )
{
  float ret = 0;
  BYTE* fBin = reinterpret_cast<BYTE*>( &ret );
 
  BYTE bin[ sizeof(val) ];
  memcpy( bin, &val, sizeof(val) );
 
  // s
  fBin[3] = bin[7] & 0x80;
 
  // exp
  WORD exp = (static_cast<WORD>(bin[7]&0x7F) << 8) | static_cast<WORD>(bin[6]);
  exp >>= 4;
  BYTE newExp = static_cast<BYTE>(exp - 1023) + 127;
  fBin[3] |= (newExp >> 1);
  fBin[2] = (newExp << 7);
 
  // fraction
  DWORD frac = (static_cast<DWORD>(bin[6]&0x0F)<<24) |
    (static_cast<DWORD>(bin[5]) << 16) | 
    (static_cast<DWORD>(bin[4]) << 8) |
    static_cast<DWORD>(bin[3]);
  frac <<= 3;
  BYTE* fracBin = reinterpret_cast<BYTE*>( &frac );
  fBin[2] |= fracBin[3];
  fBin[1] = fracBin[2];
  fBin[0] = fracBin[1];
 
  return ret;
}
 
int main()
{
  double val = -44100;
  std::printf("double: %f\n", val);
  print16( &val, sizeof(val) );
  std::printf("\n");
 
  float fVal = doubleToFloat( val );
  std::printf("float: %f\n", fVal);
  print16( &fVal, sizeof(fVal) );
 
  return 0;
}

 
output:

double: -44100.000000
0xc0e5888000000000
 
float: -44100.000000
0xc72c4400

 かなり力技だけど、でもビットいじくって何とかできることはわかった。
ちなみに円周率3.141592653589793238でためすとこうなった。floatでもわりと表現範囲が広いのね。

double: 3.1415926535897931
0x400921fb54442d18
 
float: 3.1415925025939941
0x40490fda

 
 ざっとしらべてたら同じようなこと考えてる人がいた。さすがにコードがコンパクト。
2007/06/24 HSP で float 型っぽい変数
http://sprocket.babyblue.jp/html/hsp_koneta3.htm#tofloat
っていうかd3mの人だ!
 
 0x38000000ってなんじゃいと小一時間ほど弄繰り回したところ大まかに把握できた、様な気がする。
doubleの仮数が20bit~31bitに存在するのでこの数値を、
20bitシフト(>>20)し、げたを脱がせ(-1023)た後に再びげたを履かせる(+127)れば、float用の仮数を生成できる。
速度を考えるといちいち>>20なんてビットシフトしなくても(-1023<<20) + (127<<20)、
つまり(-896<<20)をすればいいじゃない、となる。
ビット操作なんだからunsignedがいいよね! ってわけで-(893<<20)のほうがよいか。
doubleの上位4Byte - (896<<20)を16進数にすれば
doubleの上位4Byte - 0x38000000 といった具合で謎だった数値が出てくる。

0 件のコメント: