IEEE754-2019 に従って倍精度浮動小数点数を実装するノートです. 初回は比較演算子についてですが, Equal
ができればあとはだいたい同じなので Equal
についてのみ述べます.
以下で使う F64
の定義はこちら.
# [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 列であったとしても).
- 一方が NaN ならば
- ゼロの比較は符号を無視して行う.
- つまり
-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
が成立しています.
- この仕様が Rust で
- invalid 例外については上に述べた通り.
- 例えば
これだけわかれば素直に実装できます. なお, 今は倍精度のみ実装しているので関係ないですが, 精度の異なるデータ (例えば単精度と倍精度) についても (共通の基数を持つならば) 比較できるようにしておく必要があります.
実装
コード中の is_signaling
, raise_flags
, StatusFlag
については別記事にする予定なのでとりあえず詳細な説明は省略しますが, 要は signaling NaN かどうかを判定して invalid シグナルを発する処理です.
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 できます.
impl PartialEq for F64 {
fn eq(&self, other: &F64) -> bool {
compare_quiet_equal(*self, *other)
}
}