[Rust] IEEE754浮動小数点数の実装 (1) 比較演算子

IEEE754-2019 に従って倍精度浮動小数点数を実装するノートです. 初回は比較演算子についてですが, `Equal` ができればあとはだいたい同じなので `Equal` についてのみ述べます.

以下で使う `F64` の定義はこちら.

```rust
#[derive(Debug, Clone, Copy)]
pub struct F64 {
    v: u64,
}

impl F64 {
    pub fn to_bits(self) -> u64 { self.v }
    pub fn from_bits(v: u64) -> F64 { F64 { v } }

    pub fn is_nan(self) -> bool {
        let a = self.to_bits();
        // exponent がすべて 1 && significand が 0 ではない
        (!a & 0x7ff0000000000000 == 0) && (a & 0x000fffffffffffff != 0)
    }
}
```

# 仕様
IEEE754 §5.6.1 に掲げられているサポートしなければならない比較演算子のうち等号 (`Equal`) に関するものは以下の4つです (pp. 43-44 の table 5.1, 5.2 もご覧ください).

| 返り値   | 関数名                    | `true` になる条件 | 備考       |
|:-------:|:------------------------:|:----------------:|:----------:|
| boolean | compareQuietEqual        | equal            | `=` に相当  |
| boolean | compareQuietNotEqual     | equal 以外        |`!=` に相当 |
| boolean | compareSignalingEqual    | equal            |           |
| boolean | compareSignalingNotEqual | equal 以外        |           |

より詳細な仕様は §5.11 に記述されています. これを読むと次のことがわかります.

* すべての浮動小数点数の二つ組は, `equal`, `less than`, `greater than`, `unordered` のいずれか.
    * 一方が NaN ならば `unorderd` (たとえふたつの入力が同一の bit 列であったとしても).
* ゼロの比較は符号を無視して行う.
    * つまり `-0` と `+0` は `equal`.
* 正負の無限大は符号も含めて一致するとき `equal`.
* signaling NaN が入力された場合は不正 (invalid) な演算となる.
    * `Quiet` とついている関数は, signaling NaN が渡された場合のみ invalid シグナルを出す.
    * `Signaling` とついている関数は, quiet NaN が渡された場合にも invalid シグナルを出す.
* `Not` とついている関数の返り値は, ついていない関数の返り値の論理的否定に等しい.
    * 例えば `unorderd` は `NotEqual` を true にする.
        * この仕様が Rust で `f64` が `PartialEq` であるが `Eq` ではない理由です. `Eq` は同値類なので `a == a` が常に成り立つことが要求されますが, `NaN != NAN` が成立しています.
    * invalid 例外については上に述べた通り.

これだけわかれば素直に実装できます. なお, 今は倍精度のみ実装しているので関係ないですが, 精度の異なるデータ (例えば単精度と倍精度) についても (共通の基数を持つならば) 比較できるようにしておく必要があります.

# 実装

コード中の `is_signaling`, `raise_flags`, `StatusFlag` については別記事にする予定なのでとりあえず詳細な説明は省略しますが, 要は signaling NaN かどうかを判定して invalid シグナルを発する処理です.

```rust
pub fn compare_quiet_equal(a: F64, b: F64) -> bool {
    if a.is_nan() || b.is_nan() {
        if self.is_signaling() || other.is_signaling() {
            raise_flags(StatusFlag::Invalid);
        }
        false
    } else {
        let ua = a.to_bits();
        let ub = b.to_bits();

        // ビット列として一致 || 入力がどちらも `\pm 0` (符号は無視する)
        (ua == ub) || ((ua | ub) & 0x7fffffffffffffff == 0)
    }
}

pub fn compare_quiet_not_equal(a: F64, b: F64) -> bool {
    !compare_quiet_equal(a, b)
}

pub fn compare_signaling_equal(a: F64, b: F64) -> bool {
    if a.is_nan() || b.is_nan() {
        raise_flags(StatusFlag::Invalid);
        false
    } else {
        let ua = a.to_bits();
        let ub = b.to_bits();

        // ビット列として一致 || 入力がどちらも `\pm 0` (符号は無視する)
        (ua == ub) || ((ua | ub) & 0x7fffffffffffffff) == 0)
    }
}

pub fn compare_signaling_not_equal(a: F64, b: F64) -> bool {
    !compare_signaling_equal(a, b)
}
```

これを使えば `PartialEq` を impl できます.

```rust
impl PartialEq for F64 {
    fn eq(&self, other: &F64) -> bool {
        compare_quiet_equal(*self, *other)
    }
}
```

# 参考文献
* [IEEE754-2019](https://standards.ieee.org/standard/754-2019.html)
* [Berkeley SoftFloat 3e](https://github.com/ucb-bar/berkeley-softfloat-3)
* [浮動小数点数 - Wikipedia](https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0)