# 固定小数点演算
コーデック内のあらゆる値は整数です。浮動小数点はどこにも使われておらず、これは
変更可能な実装上の都合ではありません。**このコーデックは演算そのもので規定
されて**います。丸め方の違うデコーダは同じ入力から異なるビットストリームを生み、
2つの実装は乖離します。
プリミティブは `src/fixed.rs` にあり、残りのコードはすべてその上に書かれています。
## Q 表記
Q*n* 値は、2ⁿ で割った分数として読むべき整数です。
| Q15 | −1.0 … 0.99997 | 信号、利得、大半の係数 |
| Q13 | −4.0 … 3.9999 | 線スペクトル周波数(π = 25736) |
| Q12 | −8.0 … 7.9998 | 直接形の予測係数 |
Q15 では 1.0 を表現できません。係数が1でなければならない箇所では Q14 の 16384
を使うか、その場合を別扱いします。一部の表の値が本来の半分に見えるのはこれが
理由の1つです。
## 3つの性質
この演算は16ビット固定小数点機の3つの性質を再現します。通常の整数演算の感覚で
書いたコードでは一致しないほど特異です。
### 1. 小数乗算
すべての積は左に1ビットシフトされます。Q15 × Q15 は本来 Q30 に落ちますが、
この余分なシフトで Q31 になり、アキュムレータの上位半分がそのまま格納できる
Q15 値になります。
```rust
pub fn mul(x: i16, y: i16) -> i64 {
(x as i64) * (y as i64) * 2
}
```
この倍化のため、多くの表の値は半分で格納されています。0.8 であるべき係数は
Q15 の 0.4 として持たれ、乗算が倍に戻します。
### 2. 巻き戻る広いアキュムレータ
アキュムレータは40ビットで、飽和ではなく**巻き戻り**ます。飽和はコードが明示的に
要求した箇所でだけ起こります。
```rust
pub fn acc(v: i64) -> i64 { (v << 24) >> 24 } // 40ビットへ畳み込む
pub fn sat(v: i64) -> i64 { v.clamp(i32::MIN as i64, i32::MAX as i64) }
```
読み手が最も躓きやすい性質です。32ビットを溢れた中間和はクリップされません
——巻き戻り、その巻き戻った値が使われます。いくつかの箇所ではアルゴリズムが
それに*依存*しています。大きなサブフレームで累算した電力が、溢れた状態を通過して
戻ってくることがあります。「安全のため」`acc` を飽和に置き換えると出力が変わります。
32ビットの上にある8ビットはヘッドルームです。32ビット積の長い累算を中間クリップ
なしに走らせるためのもので、相関と電力のループがまさにそれを必要とします。
### 3. 明示的な丸めと切り捨て
丸めは言語のものではありません。コーデックが丸める箇所では、bit 15 に半分を加え、
それ未満を捨てます。
```rust
pub fn round(v: i64) -> i64 { acc(v + 0x8000) & !0xffff }
```
そして値が16ビット記憶を経由する箇所では、コードがそう明言します。`trunc32` は
格納・読み出しが往復させる32ビットだけを残し、それより上のアキュムレータビットを
捨てます。
## 除算
模擬すべき除算命令がないので、除算は条件付き減算とシフトの繰り返しで構成します。
- `restoring_divide` — 1ステップで商1ビットを出すプリミティブ;
- `normalised_reciprocal` — 正規化してから 16383 を絶対値で割り、商と指数を返す;
- `divide` — Q15 比のスケールを保つ符号付き正規化除算;
- `sqrt`, `inverse_sqrt` — 2分探索による平方根と、その逆数。
コーデックの大半は除算そのものを回避します。「どちらの候補が良いか」の比較は
すべて比であり、比は交差乗算で比較します。`a/b > c/d` は `a·d > c·b` になります。
探索は一度も除算しません。
## 正規化
指数部がないので、ダイナミックレンジは手で管理します。全体を通じた定石は:
1. 値の冗長な符号ビット数を数える(`exp`);
2. その分だけ左シフトし、語長を使い切らせる;
3. 最大精度で演算する;
4. シフト量を結果と一緒に持ち回り、最後に適用する。
いくつもの関数が仮数と指数の対を返すのはこのためであり、利得探索の最初の仕事が
5つのそうした対を共通の指数へ揃えてから加算することなのも同じ理由です。
## コードを読むために
演算を読みやすくする習慣が2つあります。
- **`i64` は数値ではなくアキュムレータ。** 下位16ビットが「下位半分」、
bit 16–31 が格納される「上位半分」、bit 32–39 がヘッドルームです。`hi` と
`low` がその2つの半分を指します。
- **`acc` は実機が巻き戻したはずの場所の印。** 防御的な記述ではなく、仕様の
一部です。取り除きたくなったなら、それが畳み込んでいる値は、アルゴリズムが
畳み込まれた姿で見ることを期待している値です。