加速度センサーから取ってきた値をシリアルモニタに表示するときに、ビット演算を使って2個のレジスタの値を合体させていました。
x = readRegister(REG_X1) | (readRegister(REG_X2) << 8);
Serial.println(x, HEX);
1個目のレジスタが下位8ビット、2個目のレジスタが上位8ビットなので、2個目を8ビット左シフトさせてから合体させてやればいいだろうと。
こう書いていると、まれにおかしな値が出ることが。
38
47
FFFFFFF8 // ←?
FFFFFFF7 // ←?
センサーを動かしながら値を取っているので、大きく変動することはあると思うんですが、そもそも桁数がおかしい。
実験してみた
どうやら2個目のレジスタがFF
とかFE
あたりのときに発生しているらしく、シフト演算と関係ありそうなので実験してみました。
int a = 255; // 0xFF
Serial.println(sizeof(int)); // => 2 つまりintは16ビット
Serial.println(a << 4, HEX); // => FF0 ←わかる
Serial.println(a << 8, HEX); // => FFFFFF00 ←?
Serial.println(a << 12, HEX); // => FFFFF000 ←??
Serial.println(a << 16, HEX); // => 0 ←わかる(あふれた)
sizeof
で返ってくる値からも、intは16ビットと思われるので、FFFFFF00
みたいな桁数はおかしい。
更に実験してみた
リファレンスに関係がありそうな記載があったので、更に実験してみました。
最上位ビットが1のxを右シフトするとき、結果はxの型に依存します。xがint型ならば最上位ビットは符号ビットですが、深遠な歴史的経緯に基づき、その符号ビットが右側にコピーされていきます。
今回やってるのは 左シフト なんだけど、何だか挙動が似てるぞ……!
というわけで、unsigned int
でやってみました。
unsigned int b = 255;
Serial.println(b << 4, HEX); // => FF0
Serial.println(b << 8, HEX); // => FF00
Serial.println(b << 12, HEX); // => F000
Serial.println(b << 16, HEX); // => 0
Serial.println();
思った通りの動きになってる!!
けど、今回のとは関係ない気がする。1
で埋められるのは右シフトの時ですし。
原因究明
もしかしたらSerial.print
で表示するときに何かしているのでは?と思い、実装箇所を探してみました。(正直、探すのに一番時間かかった……。)
Arduino/hardware/arduino/avr/cores/arduino/Print.cpp
size_t Print::print(int n, int base)
{
return print((long) n, base);
}
long
にキャストしてる!!
何だか分かってきたかも……。
つまり、こういうことだったみたいです。
-
int
の変数に 255 を入れる(16ビットintなので 0x00FF になる) - 8ビット左シフトすると、 0xFF00 になる
- 普通の
int
は固定小数点数なので、最上位ビットが1だとマイナスの値になっちゃう( -256 を表す) -
long
にキャストしたときに、同じ-256を表すように隙間を1で埋められる - 16進表記すると FFFFFF00 って表示される
分かってしまえば凄く単純で初歩的な事でした。
今回の教訓
- シフト演算するときは
unsigned int
にする -
int
やlong
のサイズが色々あることを意識する