組込みに欠かせない Elixir でのビットの扱い方

これは [fukuoka.ex Elixir／Phoenix Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fukuokaex) の20日目です。
昨日は [@akkiii](https://qiita.com/akkiii) さんの [RubyプロダクトをElixirプロダクトへリプレイスする作業を自動化しよう①　fukuoka.ex Elixir／Phoenix Advent Calendar 2020【5分で読める】](https://ruby-deve.hatenablog.com/entry/2020/12/20/001337) でした。

# はじめに
一月ぐらい前に「さあアドベントカレンダーの季節だ。しばらく放っておいた LiveView の再訓練をして記事を書くぞ〜」とか思ってたのですが、まあ、ありがたいことに非Elixir業務が千客万来で身動き取れず。アドベントカレンダーのタイトルに Phoenix が入っているところで、また分類が「Webテクノロジー」であるところで大変申し訳ございませんが、Phoenix とは全く独立のネタでございます。ご査収のほど何卒よろしくおねがい申し上げます。

で、何かを書くかというとビット操作の話です。いえね、Elixir / Erlang を使う理由は各自で色々とあるとは思いますが、ビット操作（とパターンマッチの組み合わせ）だけでも使う価値があるのではないかと思うほど便利なんですよ。一番使うシーンは Nerves など使って IoT する場合でしょう。加えて、通信プロトコルの実装を作るときなども大変便利だと思います。

ところがところが、ビット操作だけでキレイにまとまってるドキュメントがあまり無いようなんです。一つには Binary 型が文字列と大変密接に関係するので、文字列の方での扱いがメインになってしまうという嫌いがあるやに思います。そこで自分への備忘録を兼ねてここに書き記しておく次第です。

## Bitwise について（追記 2021.12.30)
この記事のあと「なんで Bitwise に触れないのか」というリクエストを頂いたので、2021年のアドベントカレンダーの記事にしました。合わせてご覧ください。

- [ビット操作関数 Bitwise を使ってみる](https://qiita.com/kikuyuta/items/7212ea7c190b303aa606)

# n進数表記
ビット操作をしていると 2, 8, 16 進数を使いたくなります。それも複数バイトとかビット列とかで表現したくなります。まず、どんな表記が可能かを見ていきます。

## n進数のリテラル
まずは 2, 8, 16 進数での表記から。

10進数はそのまま、2進数は `0b` を前につけて表現します。

```exs
iex(1)> 104
104
iex(2)> 0b1101000
104
```

これ先頭に `0` を付けても値は変わりません。また任意の場所に `_` を挿入することが出来ます。

```exs
iex(3)> 0b01101000
104
iex(4)> 0b0110_1000
104
iex(5)> 0b01_101_000
104
```

8進数は `0o` （最初がゼロで次がオーであることに注意）を使います。先頭の `0` と `_` の扱いも一緒です。

```exs
iex(6)> 0o150
104
iex(7)> 0o0150
104
iex(8)> 0o000_150
104
```

16進数は `0x` を使います。この `x` ってのは `hexadecimal` の `x` と信じてるのですが、どなんでしょね。ここでも先頭の `0` と `_` の扱いは一緒です。

```exs
iex(9)> 0x68
104
iex(10)> 0x068
104
iex(11)> 0x0000_0068
104
```

さてここで注意してほしいのは、上で書いたリテラルは全部 Elixir での普通の整数です。組込みをやってる文脈ですと `0x68` と書くと1バイトで `0x0000_0068` と書くと2バイトのような気がしたりします。しかし実際にはそんなことはなく、いつもどおりの任意桁数の整数になります。次のような例でも、1バイトの大きさに丸められたりはしません。

```exs
iex(15)> x = 0x68
104
iex(16)> x + 0x98
256
```

## バイナリ (Binary)
Elixir ではバイト列をバイナリ (binary) と呼びます。組込みでもたいていはバイト単位で入出力を扱うのでバイナリをよく使います。が、これちょっと紛らわしい言葉遣いです。

- 8bit区切りの列のみを指す（2進数の意味の binary ではない）
- 文字列と互換性がある

このため、後で出てくるビット列と混乱しやすい上に、何かと文字列扱いにされがちなので注意してください。バイト列で扱いたくても積極的にUTF-8の文字列扱いにされてしまいます。

バイナリ（バイト列）は以下のように `<<` と `>>` とで挟みます。先程の値を1バイトだけからなるバイナリにしてみます。

```exs
iex(14)> <<104>>                        
"h"
iex(15)> <<0b1101000>>
"h"
iex(16)> <<0o150>>    
"h"
iex(17)> <<0x68>> 
"h"
```

と該当する ASCII のコードで印字されてしまいます。なので以下のように無機質なバイト列を与えたにも関わらず、人間が余計な意味を感じてしまうようなことも起こりえます。

```exs
iex(25)> <<104, 0b1100101, 0o154, 0x16c, 111>>
"hello"
iex(26)> <<77, 0b1100101, 0o162, 0x72, 121>>
"Merry"
```
目的の n進数 で出力する方法はこの後で紹介します。

そしてややこしいことに、このカンマで区切られた一つ々々は1バイトになります。演算の結果が 0〜255 の範囲を超えた場合は丸められます。
以下はわかりにくいですが `h` の ASCII コードが 104 で、それに 256 を足しても引いても同じ値に丸められることを示しています。下の2つは、英小文字から 32 だけ小さい方にずらすと ASCII コードで大文字が該当するので、丸めた結果が `72` になって大文字の `H` になってる様子です。

```exs
iex(32)> <<104+256>>                                
"h"
iex(33)> <<104-256>>
"h"
iex(34)> <<104+224>>
"H"
iex(35)> <<104-288>>
"H"
```

なお、この `<<` と `>>` は特殊な記法ですが、れっきとした [`<<>>/1`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1) 関数扱いであることに驚いてください。そのへんの不思議な文法に基づく記法は他のも含めて [Kernel.SpecialForms](https://hexdocs.pm/elixir/Kernel.SpecialForms.html) に記載があります。

### 長さを測る
以上のようなバイト列の長さを測るには `byte_size/1` を用います。

```exs
iex(36)> byte_size(<<104, 0b1100101, 0o154, 0x16c, 111>>)    
5
```

`String.length/1` というのもあります。しかしこれ、バイト長を見るのには使わないでください。この関数は文字列リストに対しても使えて、その場合はバイト数ではなく UTF-8 で見た場合の文字数を返します。バイナリは各要素が1バイトなのでバイナリに限っては同じ値を返すと思われますが、何かと混乱のもとになるので避けたほうが賢明です。

## ビット列 (Bitstring)
上ではバイト列を扱いましたが、必ずしも8bitでない長さを直接扱いたい場合が出てきます。そういう場合に bit 長を明示して記述する方法があります。

```exs
iex(24)> <<0b1101000::size(7)>>
<<104::size(7)>>
```

はい、今度は `h` とか言われません。7bit 長のパターンが出来てます。以下のようにも記述できます。

```exs
iex(29)> <<104::size(7)>>       
<<104::size(7)>>
iex(30)> <<0o150::size(7)>> 
<<104::size(7)>>
iex(31)> <<0x68::size(7)>> 
<<104::size(7)>>
iex(32)> <<0x0000_0068::size(7)>>
<<104::size(7)>>
```

几帳面な方向けに `size` を使いましたが、以下のように省略も出来ます。

```exs
iex(38)> <<104::7>>                
<<104::size(7)>>
iex(39)> <<0o150::7>>              
<<104::size(7)>>
iex(40)> <<0x68::7>> 
<<104::size(7)>>
```

さらにこうも書けます。どれも 7bit 長の `1101000` を表現しています。そしてすごいことに、同じ値をこういう風にも記述できます。

```exs
iex(36)> <<0xd::4, 0b000::3>> 
<<104::size(7)>>
```

これは4bitの `1101` に3bitの `000` をつないだビット列です。さらにこうも書けます。

```exs
iex(37)> <<0b110::3, 0o2::2, 0::2>>
<<104::size(7)>>
```

これは3つのパートから出来てますが `110` `10` `00` をつなげて `1101000` を構成しています。盛り上がってきましたねぇ。そうこなくっちゃです。

### 8n bit長でないビット列はバイナリではない

上の7bitの値は先程の1バイトのバイナリと同様のビットパターンのように見えますが、ビット長が違います。試しに先程のバイナリと比較してみましょう。

```exs
iex(25)> <<0b1101000>> == <<0b1101000::size(7)>>
false
```

と、異なる値であることがわかります。これを明示的に 8bit として指定すると同じバイナリになります。

```exs
iex(26)> <<0b1101000>> == <<0b1101000::size(8)>>
true
```

なお `byte_size/1` 関数で見ると、収容しているバイト長が返ってきます。7bitのビット列は1バイトの箱に収容されているので 1 byte として扱われます。

```exs
iex(45)> byte_size(<<0b1101000::size(7)>>)                              
1
iex(46)> byte_size(<<0b1101000::size(8)>>)
1
iex(47)> byte_size(<<0b1101000::size(7)>>) == byte_size(<<0b1101000::size(8)>>)
true
```

# n進数出力
さて、これまで値の出力はバイナリの場合で印字可能な場合は文字列で、そうでない場合は10進数で出力されてきました。当然 2, 8, 16 進数でも出力したいですね。`inspect/2` 関数や `IO.inspect/2` 関数で出力可能です。以下では `inspect/2` を使ってみます。

```exs
iex(74)> inspect(104)
"104"
iex(75)> inspect(104, base: :binary)
"0b1101000"
iex(76)> inspect(104, base: :octal) 
"0o150"
iex(77)> inspect(104, base: :hex)  
"0x68"
iex(78)> inspect(0b1101000, base: :hex)
"0x68"
iex(79)> inspect(0x68, base: :binary)  
"0b1101000"
```

このように変換可能です。ビット列の場合も同様です。

```exs
iex(80)> inspect( <<0b110::3, 0o2::2, 0::2>>, base: :binary)    
"<<0b1101000::size(7)>>"
iex(81)> inspect( <<0b110::3, 0o2::2, 0::2>>, base: :hex)   
"<<0x68::size(7)>>"
```

第1引数に値が来れば良いので、こんな風にも使えます。

```exs
iex(82)> 0b1101000 |> inspect(base: :hex)                   
"0x68"
iex(83)> 0x68 |> inspect(base: :binary)  
"0b1101000"
iex(84)> <<0b110::3, 0o2::2, 0::2>> |> inspect(base: :binary)
"<0b1101000::size(7)>>"
```

inspect の結果は第1引数の値そのものが出てきます。関数を連ねるパイプの間に挟む場合にも使える `inspect` 関数らしい便利な使い方が可能です。

なお、ここで取りうるオプションはまだまだあります。詳しくは [Inspect.Opts](https://hexdocs.pm/elixir/Inspect.Opts.html) を御覧ください。

# パターンマッチ
Elixir / Erlang のバイナリやビット列が強力なのは、当然ながらパターンマッチにも使えることです。まず、先程の1バイトのバイナリでパターンマッチしてみましょう。

```exs
iex(113)> x = <<0b1101000>>
"h"
iex(114)> <<a::size(4), b::size(2), c::size(2)>> = x
"h"
iex(115)> IO.inspect(a, base: :binary)              
0b110
6
iex(116)> IO.inspect(b, base: :binary)              
0b10
2
iex(117)> IO.inspect(c, base: :binary)              
0b0
0
```

と、このように 4bit, 2bit, 2bit でマッチが出来ました。このマッチはより単純に

```exs
iex(119)> <<a::4, b::2, c::2>> = x                  
"h"
```
と書けますので大変簡便にビット単位でのマッチが可能であることがわかります。複数バイトでも同様にマッチさせることが出来ます。

```exs
iex(92)>  x = <<104, 0b1100101, 0o154, 0x16c, 111>>
"hello"
iex(93)> <<a::16, b::16, c::8>> = x                
"hello"
iex(94)> IO.inspect(a, base: :hex)                 
0x6865
26725
iex(95)> IO.inspect(b, base: :hex)                 
0x6C6C
27756
iex(96)> IO.inspect(c, base: :hex)                 
0x6F
111
```

全部をマッチさせるのではなく「先頭1byteだけマッチさせて、残りをマッチ」というのもできます。

```exs
iex(126)> <<p::8, rest::binary>> = x     
"hello"
iex(127)> p
104
iex(128)> rest
"ello"
```

このように、bit長の代わりに `binary` と記述しておくと可変長マッチをしてくれます。

## 型の指定
上ではなにげにさらりと `binary` と書けば可変長でマッチすると書きましたが、厳密には 「`rest` 変数に `binary` 型の値が収まるようにマッチするようにせよ」という指示になります。この `binary` の他にいくつかの型を記述することが可能です。

ここに来ることの出来るオプションの型指定はバリエーションが多く、組込みでもかなり使えそうです。公式ドキュメントの [オプションの型指定の例](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1-options) によると以下のような指示が出来て、これらは全部同じマッチになります。

```exs
iex(159)> <<102::integer-native, rest::binary>> = "foo"
"foo"
iex(160)> x = <<0b1100110, 0b1101111, 0b1101111>>      
"foo"
iex(161)> <<102::integer-native, rest::binary>> = x    
"foo"
iex(162)> <<102::native-integer, rest::binary>> = x
"foo"
iex(163)> <<102::unsigned-big-integer, rest::binary>> = x
"foo"
iex(164)> <<102::unsigned-big-integer-size(8), rest::binary>> = x
"foo"
iex(165)> <<102::unsigned-big-integer-8, rest::binary>> = x
"foo"
iex(166)> <<102::8-integer-big-unsigned, rest::binary>> = x
"foo"
iex(167)> <<102, rest::binary>> = x
"foo"
```

先頭が `102` (= "f") で、残りをいろんな型表現で `rest` 変数にマッチさせてます。このマッチを一番厳密に表現している型表記が `unsigned-big-integer-size(8)` です。

- 符号なし
- ビッグエンディアン
- 整数
- 8bit

でマッチするようにという指示になっています。これらの型は順序に関係なく `-` で結べば良いことになってます。そして、単に `binary` とやるとデフォルトがこの設定になります。

詳しくは [Kernel.SpecialForms の ::/2 関数の型](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1-types)を参照してください。

### 浮動小数点表記を解析する
この型指定でデータ型がどう表現されているかも覗けます。例えば「1.0 って浮動小数点形式ではどのように表現されるのだろう」などと思ったとすると、次のように瞬時に解決できます。

```exs
iex(132)> <<1.0::float>> |> inspect(base: :binary)
"<<0b111111, 0b11110000, 0b0, 0b0, 0b0, 0b0, 0b0, 0b0>>" 
```

これをより詳しく、符号、指数部、仮数部、に分けて見てみたいなと思っても一発です。[IEEE754の倍精度](https://ja.wikipedia.org/wiki/IEEE_754#64ビット倍精度の交換形式) では、符号ビットが先頭1bit、指数部が続く11bit、仮数部が残りの52bit、と決まっていますので、以下のように書くとうまくマッチできます。

```exs
iex(146)> <<sign::1, exp::11, frac::52>> = <<1.0::float>>    
<<63, 240, 0, 0, 0, 0, 0, 0>>
iex(147)> inspect(sign, base: :binary)                       
"0b0"
iex(148)> inspect(exp, base: :binary) 
"0b1111111111"
iex(149)> inspect(frac, base: :binary)
"0b0"
```

これ、符号は 0 が正です。指数部が 0b1111111111 (=1023) なのは規格で 1023 だけオフセットするとなってるからで、要は `1023 - 1023 = 0` で指数部は `0` です。仮数部が `000...000` になってるのは、仮数部を正規化したことで先頭に来る `1` を省略する（いわゆる「ケチ表現」にする）ことになってるからで、もとの仮数部のビット列は `1000...000` です。つまり $+1.000...000 \times 2^0$ を表現しているとわかります。

負の数の表現は？とか、2進数だと無限小数になるパターンはどうだろうとか思ってもすぐに分かります。

```exs
iex(150)> <<sign::1, exp::11, frac::52>> = <<-1.0::float>>
<<191, 240, 0, 0, 0, 0, 0, 0>>
iex(151)> inspect(sign, base: :binary)                    
"0b1"
iex(152)> inspect(exp, base: :binary)                     
"0b1111111111"
iex(153)> inspect(frac, base: :binary)                    
"0b0"
iex(154)> <<sign::1, exp::11, frac::52>> = <<1/10::float>>
<<63, 185, 153, 153, 153, 153, 153, 154>>
iex(155)> inspect(sign, base: :binary)                    
"0b0"
iex(156)> inspect(exp, base: :binary)                     
"0b1111111011"
iex(157)> inspect(frac, base: :binary)                    
"0b1001100110011001100110011001100110011001100110011010" 
```

浮動小数点の表現方法を知っていても、実際にビットパターンがどうなるのかを詳細に考えるのは大変です。簡単に具体的にこのように見えると大変おもしろいですね。

## 応用例

このビット列のパターンマッチは、組込みやってると超絶便利です。以下は[私が以前に書いた SPI 通信のプログラム](https://qiita.com/kikuyuta/items/a5a0dd72ef926299be7e#半固定抵抗の値を連続的に読んでみる) の一部です。

```exs
    {:ok, val} = SpiInOut.send_receive(spi_name, inst)
    <<_::size(12), val12bit::size(12)>> = val
```

SPI 通信はちょっと私には不思議な全二重の通信方式です。どう不思議かというと、送信コマンドが終わり切る前に、結果の値が返り始めるのです。ここで使ってた SPI デバイスはアナログ・デジタルのコンバータで、コマンドの送信途中で変換結果が返り始めます。全体で24bitのレスポンスが得られ、先頭12bitを無視して後ろの12bitを用いると意味を持つ値になります。


このプログラムでは、SPI デバイスから返ってきた値を一旦全部 `val` 変数に格納し、次の行で先頭12bitを `_` にマッチさせて捨ててしまい、残りの12bitを `val12bit` に格納します。

これを普通のプログラミング言語で書くと、まあ面倒な書き方になりますし、後で見たときの可読性がよろしくありません。が、Elixir ではパターンマッチ一発で記述ができて、可読性も良いです。

これ当然 Elixir ってぐらいで、関数の引数でマッチさせて if を使わずに条件分岐させることも出来ます。
[公式ドキュメント](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1-binary-bitstring-matching) に、ファイルの先頭を見て :png か :jpg かを判定するプログラム例がありますので合わせて御覧ください。

# まとめ
Elixir / Erlang で超絶便利なビット列の扱いについてまとめてみました。様々な記法が可能な上に、Elixir / Erlang ならではの強力なパターンマッチにも使うことが出来て、特に組込みや通信プロトコルのプログラミングでは便利に使えます。そうですね、Erlang が元々電話交換機用の言語として設計されたという生い立ちを考えると、ビット操作が便利なのも宜なるかな、です。

なお、ビット列の操作をするのには [Bitwise](https://hexdocs.pm/elixir/Bitwise.html) というライブラリがあります。こちらも合わせて使うと無敵です。Bitwise についてはまた今度機会があったら書いてみます。

さて、明日の[fukuoka.ex Elixir／Phoenix Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fukuokaex) の21日目の記事は [@GKBR](https://qiita.com/GKBR) さんの [Hello! After World!! 2D - (第0章)](https://qiita.com/GKBR/items/8d4c7b475ce9d6e51821) です。お楽しみに！

おおっと忘れてた。
`<<77, 0b1100101, 0o162, 0x72, 121, 32, 0x58, 0o155, 0b01100001, 0x73, 33>>`
そして[^strings]。
`[12450, 12523, 12465, 12511, 12473, 12488, 12398, 12415, 12394, 12373, 12435,
 12289, 33391, 12356, 12362, 24180, 12434, 12362, 36814, 12360, 12367, 12384,
 12373, 12356] |> IO.puts`

[^strings]: 作り方は @torifukukaiou さんの2019年のアドベントカレンダー[12月3日なので、一二三、123ダーなElixirのこと](https://qiita.com/torifukukaiou/items/8c37f9710e45b50b6aba#メッセージの作り方) を参考にしてください。


# 参考文献
`for` との組み合わせが楽しいとの話を頂きました。ちょうど [@koga1020](https://qiita.com/koga1020) さんが記事をお書きになってましたので先頭にあげておきます。どうぞご参照くださいませ。

- [Elixirのfor文をおさらいしてみる bitstringを処理する](https://zenn.dev/koga1020/articles/903d6a6bdde26b#bitstringを処理する)
- [Elixir School, Basics, Integers](https://elixirschool.com/en/lessons/basics/basics#integers)
 - [Elixir School, 基本, 整数（上の翻訳記事）](https://elixirschool.com/ja/lessons/basics/basics#整数)
- [Elixir, Getting Started, Binaries, strings, and charlists](https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html)
 - [Elixir入門 06: バイナリと文字列および文字リスト（上の翻訳記事）](https://dev.to/gumi/elixir-06--35na)
- [Elixir official document, Kernel.SpecialForms](https://hexdocs.pm/elixir/Kernel.SpecialForms.html)
 - [Elixir official document, Kernel.SpecialForms, ::/2](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#::/2)
 - [Elixir official document, Kernel.SpecialForms, <<>>/1](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1)
- [Elixir official document, Inspect.Opts](https://hexdocs.pm/elixir/Inspect.Opts.html)
- [Elixir Bitwise](https://hexdocs.pm/elixir/Bitwise.html)
- [Erlang, byte_size](https://erlang.org/doc/man/erlang.html#byte_size-1)
- [はじめてNerves(5) SPIを使ったA/D変換結果をGPIOのPWMでLチカ](https://qiita.com/kikuyuta/items/a5a0dd72ef926299be7e)
- [IEEE 754](https://ja.wikipedia.org/wiki/IEEE_754)