鍋あり谷あり

テーマを決めずに適当に書いています。

シフト演算

今日、恐ろしいことに気がついた。
386互換 CPU*1上で shl eax, cl を使って eax を 32bit シフトすると、ゼロクリアされないのである。演算の結果は、シフト前と同じ。すなわち、clの下位5bit は無視されるのである。
まあCPUの命令ならそういうこともあるかもしれないが、某社のコンパイラコンパイルすると、operator<< がそのまま shl になってしまうので、

#include <stdio.h>

typedef unsigned int uint32;
typedef unsigned __int64 uint64;

template< typename T, int bits >
void test( char * format )
{
  T complietime_value = (~T(0))<<bits;
  T runtime_value = (~T(0))<<((new char)?bits:0); // to supress optimize
  printf( format, bits, complietime_value, runtime_value );
}

int main()
{
  test<uint32,31>("uint32<<%d : %x / %x\n");
  test<uint32,32>("uint32<<%d : %x / %x\n");
  test<uint64,63>("uint64<<%d : %I64x / %I64x\n");
  test<uint64,64>("uint64<<%d : %I64x / %I64x\n");
  return 0;
}

コンパイル-実行*2 すると、

uint32<<31 : 80000000 / 80000000
uint32<<32 : 0 / ffffffff
uint64<<63 : 8000000000000000 / 8000000000000000
uint64<<64 : 0 / 0

のようになる。32bit 値を 32bit シフトする計算で、runtime の計算とコンパイル時の計算で結果が違っている。どうも、コンパイル時は真面目に計算しているらしい。
bcc5.5.1 でも試してみたところ、bcc5.5.1 では

uint32<<31 : 80000000 / 80000000
uint32<<32 : ffffffff / ffffffff
uint64<<63 : 8000000000000000 / 8000000000000000
uint64<<64 : 0 / 0

と、一致した。コンパイル時の計算も shl そのまんまのようだ。
また、コンパイル時に警告が出た*3
shl そのまんまでゼロクリアされないのはどうかと思うが、多少はましか。

C++ の仕様への準拠ということでは、どうなんだろう??
まあ、x86 は珍妙なCPUのようなので、そういうこともあるだろう。なんて思ってしまう。

それと。

document.write( '1<<32 = '+(123<<32)+'<br>' )
document.write( '1<<31 = '+(123<<31)+'<br>' )

とかやったら、Opera, IE6, Mozilla FireFox のどれでも、123<<32 は 123 だった。それでいいのか??
x86 環境でどうなるのか興味あるが、持ってないのでわからない。

*1:Pentium4/Pentium3/Duron/AthlonXP-M について調査した。

*2:最適化抑止のためのインチキ計算のためにメモリリークするが、問題ない。それと。最適化抑止が不十分だと、最適化レベルによって結果が変化する。

*3:某社のコンパイラの場合、警告レベルを上げても警告は出なかった。