Kotlin と Bit単位のデータを扱う
IoTやBLEやHTTPじゃないプロトコルで小さなデータをやり取りするときにまれーーーに出てくるBit単位のデータのやり取りについて、最近会話しながら思ったことがあったので、復習がてらKotlinとBitについて記事にします。
二進数表記
簡単なところから 0b
を先頭につければ二進数であることを表記できます。下記は 2^4-1 = 15 を表現しています。
val num = 0b1111
二進数表記については型は関係ないので、 Int でも Long でも使えます。
val num1: Int = 0b1111
val num2: Long = 0b1111
こんな感じで 1 を 63 個並べれば Long.MAX_VALUE と同じ 9223372036854775807
になります
val max = 0b111111111111111111111111111111111111111111111111111111111111111
assert(max == Long.MAX_VALUE)
Byte型
下記を定義しているとします。 8bit = 1byte 分のbitが全て立っている状態です。
val num: Long = 0b11111111
このとき num.toByte()
は Byte.MAX_VALUEになるでしょうか?
結論、これはNoです。この結果は -1
になります。
val num: Long = 0b11111111
println(num.toByte()) // このアウトプットは -1 になる
というのも、Byte型の時は 先頭の bit で符号を表現するためです。 Byte.MAX_VALUE は 0b01111111
が正しいです。
val max: Long = 0b01111111
assert(byteMax.toByte() == Byte.MAX_VALUE)
Byte.MIN_VALUE は 0b10000000
で表現できます。
val byteMin = 0b10000000
assert(byteMin.toByte() == Byte.MIN_VALUE)
UByte型
UByte型という 0~255 の型があります。
先ほどのこの数字。 Byte型では -1
になりました。
val num: Long = 0b11111111
これを toUByte すると、UByte.MAX_VALUE になります。こちらは符号になるbitなどがないのでこのような結果になります。
こんなデータが飛んできたら...
63bit未満のこんなフォーマットのデータがあるとします。
| <- 8byte -> | <- 7byte -> | <- 13byte -> | <- 6byte -> |
| seq_no | payload1 | payload2 | payload3 |
このとき 1111110110001001111111100111000100
というデータを受信した場合に
- seq_no => 11111101
- payload1 => 1000100
- payload2 => 1111111100111
- payload3 => 000100
というふうにデータを抽出したいとします。
いろんなやり方あると思うのですが、自分だったらこうするかなという実装です。
val target: Long = 0b1111110110001001111111100111000100
val MASK_SEQ_NO: Long = 0b1111111100000000000000000000000000
val MASK_PAYLOAD1: Long = 0b0000000011111110000000000000000000
val MASK_PAYLOAD2: Long = 0b0000000000000001111111111111000000
val MASK_PAYLOAD3: Long = 0b0000000000000000000000000000111111
val SIZE_PAYLOAD1 = 7
val SIZE_PAYLOAD2 = 13
val SIZE_PAYLOAD3 = 6
val seqNo = (target and MASK_SEQ_NO) ushr (SIZE_PAYLOAD1 + SIZE_PAYLOAD2 + SIZE_PAYLOAD3)
val payload1 = (target and MASK_PAYLOAD1) ushr (SIZE_PAYLOAD2 + SIZE_PAYLOAD3)
val payload2 = (target and MASK_PAYLOAD2) ushr SIZE_PAYLOAD3
val payload3 = target and MASK_PAYLOAD3
少し解説します。この部分ではそれぞれのマスクを作っています。これを用意して一覧にすることでどこに何があるかわかりやすくなるのかなと思います。
val MASK_SEQ_NO: Long = 0b1111111100000000000000000000000000
val MASK_PAYLOAD1: Long = 0b0000000011111110000000000000000000
val MASK_PAYLOAD2: Long = 0b0000000000000001111111111111000000
val MASK_PAYLOAD3: Long = 0b0000000000000000000000000000111111
次にこの部分です。
val seqNo = (target and MASK_SEQ_NO) ushr (SIZE_PAYLOAD1 + SIZE_PAYLOAD2 + SIZE_PAYLOAD3)
target and MASK_SEQ_NO
では and演算を行うので、下記のように両方で bit が立っているところだけ bit を立てます。
target => 0b1111110110001001111111100111000100
MASK_SEQ_NO => 0b1111111100000000000000000000000000
target and MASK_SEQ_NO => 0b1111110100000000000000000000000000
ushr (SIZE_PAYLOAD1 + SIZE_PAYLOAD2 + SIZE_PAYLOAD3)
の部分は右側の Payload1, Payload2, Payload3 の部分を削って右にずらします。
target and MASK_SEQ_NO => 0b1111110100000000000000000000000000
ushr (7 + 13 + 6) => 0b 11111101
これを Payload1, Payload2, Payload3 にもやってく。これでbit単位でのデータを整理できるかと思います。
あんまりそんなにこんな機会はないかも知れないですが、その時はぜひ!
Discussion