固定小数点演算用命令

# はじめに
 - この記事は[ひとりNEONアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/neon-only) 18日目の記事です
 - [昨日は`popcount`命令](https://qiita.com/tomoaki_teshima/items/05105f8fa869873f85f6)を紹介した
 - 今日は固定小数点演算用命令を紹介する


# そもそも固定小数点演算とは
 - [OpenCV アドベントカレンダー2020](https://qiita.com/tomoaki_teshima/items/0f97defc72639c2db9df)にも寄稿したが、浮動小数点演算ではなく、整数型の演算で小数点以下の数値を表現する方法である
 - キモは割り算を表す右bitシフトだが、NEONではそのあたりかゆいところに手が届く仕様になっている。
 - 代表例として、`addhn`、`raddhn`、`rshr_n`、`hadd`命令を紹介する

# `addhn`
 - 本日初紹介、固定小数点演算用加算命令、`addhn`
    - 公式によれば"Add returning high narrow"

```addhn.cpp
        uint16_t data0[] = { 640,624,608,592,576,560,544,512 };
        uint16_t data1[] = { 640,624,608,592,576,560,544,512 };
        uint16x8_t vsrc0 = vld1q_u16(data0);
        uint16x8_t vsrc1 = vld1q_u16(data1);
        uint8x8_t v_dst = vaddhn_u16(vsrc0, vsrc1);
```

```
0    640    640     5
1    624    624     4
2    608    608     4
3    592    592     4
4    576    576     4
5    560    560     4
6    544    544     4
7    512    512     4
```
 - 1番左から要素番号、第1引数の要素、第2引数の要素、そして1番右に演算結果である
 - 一見意味不明だが、これは両引数の和を256で割っている。
    - これをすることにより、1/256を最小の単位とした固定小数点数での演算結果を、整数に変換することができる。
    - 1/256なのは、結果の各要素が上位8bitのみで、右8bitシフトが256割ることと等価だから。
 - また、`high`が末尾に付くバージョンもある。こちらは、引数がさらに1つ増え、3引数を取る

```arm_neon.h
int8x16_t
vaddhn_high_s16 (int8x8_t __a, int16x8_t __b, int16x8_t __c)
```

 - 第2引数と第3引数で固定小数点数演算を行い、結果を第1引数と連結して128bitレジスタとして返される。
 - `vaddhn`と`vaddhn_high`をそれぞれ呼べば、レーン数倍になった整数型のベクトルが得られる。
 - 念の為追記しておくと、「固定小数点型」という型は存在しない。NEONでは、各要素幅`2n`bitのうち、下位`n`bit分を小数点以下を表す領域として扱う、という話。
    - なので、
    - 引数の要素が`int32_t`の場合はシフト幅は16bit、65536で割ることに相当する
    - 引数の要素が`int64_t`の場合はシフト幅は32bit、4294967296で割ることに相当する

# `raddhn`
 - `addhn`のお友達、固定小数点演算用加算命令、`raddhn`
 - `addhn`と似た演算をするが、右bitシフトして整数に戻す前に、定数を足し合わせる。
 - 例えば出力の各要素が8bit幅の場合、`addhn`命令と同じく256で割り算が行われるが、その際256の半分の128が加算される
 - これにより、`raddhn`の演算結果は「切り捨て」ではなく、「最近接丸め」、いわゆる四捨五入が行われる。

```raddhn.cpp
        uint16_t data0[] = { 640,624,608,592,576,560,544,512 };
        uint16_t data1[] = { 640,624,608,592,576,560,544,512 };
        uint16x8_t vsrc0 = vld1q_u16(data0);
        uint16x8_t vsrc1 = vld1q_u16(data1);
        uint8x8_t v_dst = vraddhn_u16(vsrc0, vsrc1);
```
 - 演算結果

```
0    640    640     5
1    624    624     5
2    608    608     5
3    592    592     5
4    576    576     5
5    560    560     4
6    544    544     4
7    512    512     4
```

 - 演算結果を`addhn`と`raddhn`で見比べてみると、演算結果が`5`になってる部分が違う。
 - `raddhn`命令では丸めを行っているため、和が`640(=128 * 5)`より小さい加算結果も、`5`に丸め込まれている。

# `rshr_n`
 - 引数の中に、単体の数値を使う場合、命令の名前中に`_n_`が含まれる

```cpp
uint16x4_t
vrshrn_n_u32(uint32x4_t a, b)
```

 - なお、第2引数はコンパイル時定数の必要があるし、ベクトル型の要素のレンジ内を指す必要がある
 - この`vrshrn_n`命令は右シフトを行う命令だが、最近接丸めを行うように、右シフト後に捨てられるbitの内、最上位bitが立ってる場合は繰り上げが行われる

```rshrn.cpp
        int32_t data[] = { 4096, 28411, 17720, 506,};
        int32x4_t vsrc = vld1q_s32(data);
        int16x4_t vdst0 = vrshrn_n_s32(vsrc, 8);
        int16x4_t vdst1 = vshrn_n_s32(vsrc, 8);
```
 - 演算結果

```
0    4096    16    16
1    28411   111   110
2    17720   69    69
3    506     2     1
```

 - 右から2列目が`vrshrn`命令の結果、一番右の列が`vshrn`命令の結果である
 - 8bit右シフト、つまり256で割ってるのだが、3番目の要素`506`みたいに、「ほぼ256の倍」の値も、ただの右シフトでは`1`になってしまうが、`rshrn`では四捨五入によって`2`と計算されていることがわかる。


# `vhadd`、`vrhadd`
 - どんどん続くよ固定小数点演算用命令
 - `vhadd`命令は加算した後右1bitシフト、
 - `vrhadd`命令は加算した後1さらに足してから右1bitシフト
 - 最小単位が1/2である固定小数点演算が可能
 - こいつらは入力と出力の型が全く同じなので、128bit幅の`vhaddq`命令、`vrhaddq`命令も提供される。

# 終わりに
 - NEONでは、浮動小数点演算を充実させる代わりに固定小数点演算が豊富にサポートされている印象を受ける
 - 多分、ArmのCPUはもともと組み込み向けがターゲットだった経緯があり、固定小数点演算が多数サポートされているのだと思う。
 - 明日も手島の執筆担当で、`mov`命令を紹介予定