🙆

Kotlin と Bit単位のデータを扱う

2023/12/21に公開

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