はじめに
softfloatライブラリとは浮動小数点数演算をソフトウェア的に実装したものです。
通常の単精度(32bit)や倍精度(64bit)の浮動小数点数演算はRustでサポートされており、ハードウェアで高速に実行されます。
しかし、それ以外の精度(例えば16bitの半精度や128bitの四倍精度)を使いたい、丸めの仕方を厳密に制御したい、といった要求には対応できません。そのような場合に、(当然ハードウェアで実行するよりはるかに遅くなりますが)ソフトウェア実装の浮動小数数演算ライブラリを使うことができます。
Rustのsoftfloatライブラリには以下のようなものがあるので、使い勝手やパフォーマンスを比較してみました。
softfloat-sys
Berkeley SoftFloatというCのsoftfloatライブラリのバインディングです。
-sysとついている通り、Cの関数をそのまま提供します。
(RustのCバインディングでは-sysというクレートでCの関数をそのまま提供し、別クレートで安全なラッパーを提供するのが一般的です)
let a = 0x12345667;
let b = 0x76543210;
let a = softfloat_sys::float32_t { v: a };
let b = softfloat_sys::float32_t { v: b };
let d = unsafe { softfloat_sys::f32_add(a, b) };
assert_eq!(d.v, 1985229328);
すべてunsafe
なので、そのまま使うのはちょっと厳しい感じです。
simple-soft-float
Rust実装のsoftfloatです。
型やトレイトがちゃんとしているので扱いやすいです。
let a = 0x12345667;
let b = 0x76543210;
let a = simple_soft_float::F32::from_bits(a);
let b = simple_soft_float::F32::from_bits(b);
let d = a.add(&b, None, None);
assert_eq!(*d.bits(), 1985229328);
rug
厳密にはsoftfloatというより任意精度演算ライブラリです。CのGMP/MPFRを使って任意精度の浮動小数点数演算をサポートしています。
(他にも任意精度整数や有理数のサポートもあります)
let a = 0x12345667;
let b = 0x76543210;
let a = rug::Float::with_val(24, f32::from_bits(a));
let b = rug::Float::with_val(24, f32::from_bits(b));
let d = a.add(b);
assert_eq!(d.to_f32().to_bits(), 1985229328);
こちらもRustっぽく使えるようになっているので使い勝手的には問題ないと思います。
たたし、IEEE754形式のビットパターンを扱う方法が見つかりませんでした。
値の初期化はf32
,f64
から行うようで、例えば直接四倍精度の値を得る方法がなさそうに見えます。
(一応MPFRの内部表現形式を直接触ることはできそうですが、それも違う感じが…)
パフォーマンス
半精度から四倍精度までの加算・乗算・除算のパフォーマンスを比較してみました。
(適当な値での計算1回だけなので、そんなに厳密な比較ではないです)
グラフの縦軸が対数だということにご注意ください。見ての通りsimple-soft-floatが圧倒的に遅いです。
もともとsimple-soft-floatのコンセプトは「速度より可読性」ということなので2桁ほど遅いことは覚悟していましたが、5桁も遅いとは思いませんでした…。
今回使ったコードはこちらになります。
https://github.com/dalance/softfloat_bench
まとめ
どれも一長一短といったところでしょうか。
パフォーマンスが必要ならsoftfloat-sys一択なので、unsafe
だけケアした簡易ラッパーを作って使うのが良さそうです。
遅くてもいいならsimple-soft-floatが使いやすいと思います。