以前作ったNCOの精度評価をきちんと行いました。その過程で面白いことがわかったのでここに記しておきます。NCOの誤差は、Blackfin用GCCの32bit固定小数点ライブラリの不適切な丸め方に強く影響されています。
32bit固定小数点乗算のオフセット
Blackfin用GCCの32bit固定小数点乗算はmult_fr1x32x32()関数を使います。この関数はアキュームレータを二つ使い、16bit乗算を4回行って32bit固定小数点乗算を行います。
このとき、アキュームレータ同士を加算するのですが、スケーリングのためにシフト演算を使用しています。結論から言うと、このシフト演算によって四捨五入ではなく負の無限大方向への切り捨てが行われるため、演算にオフセットが発生します。このオフセットは、-0.5LSBであり、乗算のたびに発生する上に常に負の値であるため、蓄積することになります。
NCOの誤差の測定値
以上のオフセットによる影響をNCOアルゴリズムで評価してみました。実際に評価を行ったのはBlackfinシミュレータ上ではなく、x86でのネイティブ実行です。x86用の固定小数点演算互換マクロを使用することによって、高速で評価できました。
Blackfin上での演算とx86上でのエミュレーション結果は全周を素数間隔で分割した約40万点での演算結果が完全一致することを事前に確認しています。その上で、全周上の40億点全点に対して固定小数点によるx86版NCOアルゴリズムの実行結果と、倍精度浮動小数点数のsin()関数計算結果を比較しました。その結果を表1に示します。
表1 NCOの誤差評価結果
四捨五入 |
切り捨て |
|
cosineの誤差の平均 |
0.038 |
-1.273 |
cosineの誤差の最大値 |
4.110 |
3.311 |
cosineの誤差の最小値 |
-4.140 |
-6.006 |
cosineの誤差の標準偏差 |
0.936 |
1.325 |
sineの評価も行いましたが、ほぼ同じ結果なので、ここでは掲載を省きました。
「四捨五入」「切り捨て」はそれぞれ32bit固定小数点乗算における、丸めの方法です。上に述べたように、Blackfin用のGCCでは「切り捨て」を使っています。
表を見ると影響ははっきりしています。切り捨てを使った場合には負の方向に統計的なオフセットが生じています。その大きさは四捨五入では1LSBの1/20程度ですが、切り捨てでは1LSBを超えています。また、誤差の振幅も「切り捨て」のほうが増えています。
なお、この誤差の振幅はいずれの方法でも8以下ですので、有効ビット数はおおむね3bit減少とみていいでしょう。32bit固定小数点演算で演算精度29bitなら、まあまあと言えます。NCOにおいてこの誤差は、統計的に単精度浮動小数点数の誤差よりも小さくなります。
精度は時間とのトレードオフになる
32bit固定小数点乗算のオフセットを減らす方法は簡単です。演算前のアキュームレータにあらかじめ0.5LSB相当の値を代入しておくのです。これで切り捨てが四捨五入に変わります。
ただし、この処理は乗算ごとに必要ですので乗算の演算性能が低下します。精度は時間とのトレードオフになります。