ビットを見つめる Part6 -理解・分解・再構築-
と、いうわけで、これまでの勉強で
「補数とは何か」
「intのマイナスはどうやって保持されているか」というものを理解できたと思います。
それでは、実際にARGBのカラー値を抜き出してみましょう。
はーい。
ここでビットシフトの話になりますが、ビットシフトはご存知ですか?
ビットの値を左か右にずらすんだよね。
まぁ、そういうことです。
種類はそれぞれ
「<<」(ビット単位の左シフト演算子)
「>>」(ビット単位の右シフト演算子)
「>>>」(ビット単位の符号なし右シフト演算子)
があります。例えば
「0×1234」という16ビットをそれぞれの演算子で扱ってみましょう。
まず「0×1234」は「0001001000110100(2)」です。「<<」は左側に指定の数だけシフトします。
例えば「0×1234 << 2」であれば、
「0001001000110100(2) << 2」ということであり、
0100100011010000(2)となります。ビットが左に2ビット分動いて、右を0で埋めた感じですね。
これ、あふれたらどうなるの?破棄されるの?
その通りです。あふれたものは破棄されます。
例えば「0×1234 << 4」としてみましょう。
「0001001000110100(2) << 4」は、0010001101000000(2)となります。
最上位から4つ目のビットまでが消えて、その分最下位から0が4つせり上がってくるんだね。
そういうことです。考え方は簡単ですよね。
では、次は「0×1234 >> 2」ですね。
「0001001000110100(2) >> 2」ということであり、右に2ビットシフトするので、
「0000010010001101(2)」ということになります。続いて、「0×1234 >>> 2」は、
「0001001000110100(2) >>> 2」ということであり、右に2ビットシフトするので、
「0000010010001101(2)」ということになります。
・・・?
同じじゃん?
そう、そこが今回の勉強内容です。
では、「0xABCD >> 6」、「0xABCD >>> 6」の場合を考えてみましょう。
それぞれどうなると思いますか?
・・・両方同じ?
いえ、残念ながら違います。
まず、0xABCDは、「1010101111001101(2)」ですね。で、両方とも右にずらすのは同じです。
問題は、「符号付きのビットシフト」か「符号なしのビットシフト」か、ということです。intの話を思い出してください。
符号はどこで判断できましたか?
最上位ビットが0だったら正の数で、1だったら負の数だったよね。
そうです。結論から言いますと、「>>>」の場合は「<<」のように、空いた分は必ず「0」で埋めます。
しかし、「>>」の場合は「符号ありの右シフト」なので、ビットシフトしても符号が反転しないように、最上位ビットが0であれば左に0を埋め、1であれば左に1を埋めます。
ということは・・・?
「1010101111001101(2) >> 6」は「1111111010101111(2)」で、
「1010101111001101(2) >>> 6」は「0000001010101111(2)」っていうことか!
そう、その通りです。数が全く異なってしまうことが判ると思います。
さて、それでは実際に色を分解してみましょう。
「0×98765432」というARGBのカラー値があります。これをARGBに分解してください。実際にAS3で作ってみてくださいね。
また、実際に2進数でも書いて、その経緯を示してください。
はーい。
まず、
var color:uint = 0×98765432;
で、色を定義して・・・。
2進数だと「0×98765432」は「10011000011101100101010000110010(2)」。
ARGBの色はuintで表現されるから、符号とか関係ないし、「>>>」を扱った方がいいのかな。
ということは、Aを抜き出す場合は、
「color >>> 24」で「00000000000000000000000010011000(2)」。
Rを抜き出す場合は・・・どうするの?
「color >>> 16」でRを下位8ビットに持っていくことはできるけど、このままだとAも交じってしまうんだけど・・・。
&演算子を使うことによって、フィルターを掛けることができます。
詳しくは省略しますが(気になる方は「ビット演算 演算子」等で検索してみてください。)、
例えば10101010(2)に&演算子で00001111(2)をかけてやると、00001010(2)となります。
えーと、つまり、同じビットのところに&で0をかけてやると、そこは0になる感じなのかな。
ということは・・・。
「color >>> 16」で「00000000000000000000010111100011(2)」になって、
それに0×000000FFを&で掛けてやればいいのか。
0xFFでいいですよ。0×000000FFと同じことなので。
お、了解!
ということは、つまり、「(color >>> 16) & 0xFF」で、「00000000000000000000000001110110(2)」。
んじゃ、Gも同じだね。
「(color >>> 8 ) & 0xFF」で、「00000000000000000000000001010100(2)」。
Bは、このフィルター掛けてやるだけでいいね。ということは、
「color & 0xFF」で、「00000000000000000000000000110010(2)」。
で、これらを10進数に直すと・・・。
A=152、R=118、G=84、B=50!
これでARGBそれぞれの値を取れた!
そういうことです。
ちなみに、Aの値を「color >> 24」でとると、「-104」で取れてしまいます。
これは実際にどのように計算されたことになりますか?
0×98765432は「10011000011101100101010000110010(2)」で、最上位ビットが1だから、1で埋めることになっちゃうね。
つまり、>>で24個シフトしたら「11111111111111111111111110011000(2)」になってるってことだよね。
で、この値は、intで表現すると、2の補数が「00000000000000000000000001101000(2)」で、104(10)だから、0-104で-104!
そう、そういうことですね。よく出来ました。
しかし、実は>>を使っても正確にAが取れる方法があります。ビットをよく見てください。「>> 24」であろうが、「>>> 24」であろうが、左側が0で埋められるか1で埋められるかの違いで、下位8ビットは同じになりますね。
ということは、「& 0xFF」を最終的に計算することによって、「>> 24」でもAが取れることになりますね。
おー、なるほどー。どういう風にビットが表現されてるかわからないと、これは確かに判らないね。
・・・でも、colorはuint型なのに、traceするとint型でしかトレースされないんだね。
そのようですね。もしuint型で表すなら「4294967192(10)」になりますね。
一応、「trace(uint(color >> 24));」のようにuintに変換してやると、上の数字で出てきますが。本来ならこのようなことをしなくてもuintで出てきてほしいところですね。
まぁでも、これですっきりしたよ!勉強になった!
やっぱり、ビットが実際にどういう風になってるかっていうのが判らないと、ビット演算を扱うのは危険なんだねー。
そうですね。その分、きちんと扱えれば、スクリプトの実行速度の向上にもつながりますし、色の分解のような、ビット演算でしかできないことも行えます。
まだまだ奥が深いビット演算、もしご興味があれば、また一つ、新たなステージに挑戦してみるのもありだと思いますよ。
さて、それでは、目標であった「色の取り出しメソッド」です。以下のコードをご覧ください。これにてこのお話は終了となります。お疲れさまでした。
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
private var color0:uint = 0x81; //B
private var color1:uint = 0x5d3; //GB
private var color2:uint = 0xf438ef; //RGB
private var color3:uint = 0xf8a13c8b; //ARGB
public function Main()
{
trace(splitColor(color0)); //129
trace(splitColor(color1)); //5,211
trace(splitColor(color2)); //244,56,239
trace(splitColor(color3)); //248,161,60,139
}
/**
* 色の数値を分解し、分解した色を配列で返します。パラメータにARGBの色を渡した場合は[A,R,G,B]、RGBの場合は[R,G,B]、GBの場合は[G,B]が返されます。
*
* @param color 分解したい色の数値。
* @return 分解された色を格納した配列。
*/
private function splitColor(color:uint):Array
{
var len:int = Math.ceil(color.toString(2).length / 8);
var returnArray:Array = [];
for (var i:uint = 0; i < len; i++)
{
returnArray[i] = (color >>> (8 * (len - (i + 1)))) & 0xFF;
}
return returnArray;
}
}
}
「俺たちの戦いはまだこれからだ!」ということで、お付き合いいただきありがとうございました。
