FPUなしで高精度な固定小数点演算の設計と実装

FPUを搭載していないマイコンでは、固定小数点演算は高速かつ省リソースな数値計算を実現する手法として広く利用されています。ただし、スケーリングや丸め誤差、オーバーフローなど注意すべき点があるため、設計を誤ると精度や制御品質に影響を及ぼす可能性があります。

この記事では、固定小数点演算の基本からQ形式の考え方、C言語での実装方法、精度を確保するための実践的な設計・テストのポイントまで解説します。

固定小数点演算の基礎知識

固定小数点演算を正しく設計するには、まず浮動小数点演算との違いや特徴を理解することが重要です。ここでは、固定小数点演算の基本的な考え方と、組み込み開発で広く利用される理由を解説します。

固定小数点演算とは

固定小数点演算とは、小数点の位置を先に固定し、整数として演算する方法です。たとえば「1.234」を、最小刻み(分解能)を0.001とするため1000倍して1234として保持すれば、整数演算だけで小数を表現し、計算できます。浮動小数点演算より表現範囲は狭くなりますが、FPUを持たないマイコンでも高速に処理できるため、リアルタイム性が求められる組み込み機器で広く採用されています。

固定小数点演算を用いるメリット

整数演算のみで処理できるため、実行時間が短く、ROM・RAM使用量やコードサイズも抑えられます。また、浮動小数点演算のような演算環境による誤差の違いが生じにくく、演算結果の再現性が高いことも大きな利点です。制御ソフトウェアでは、この予測可能性が品質向上につながります。

固定小数点演算が利用されている代表的な用途

固定小数点演算は、センサ値の補正やフィルタ処理、PID制御などのモータ制御、オーディオ・DSP処理などで広く利用されています。また、CAN通信や一般的なシリアル通信などでは、通信データを整数として送受信し、アプリケーション側でスケーリングして実数値へ変換するケースも一般的です。

固定小数点の設計方法

固定小数点のQ15形式(符号付き16bit)を説明した図。最上位1bitが符号、残り15bitが小数部で計16bit、1.15形式、表現範囲は-1.0〜0.999969482、分解能は2の-15乗(約3.05×10のマイナス5乗)。数値の表現例として、約1.0(実際は0.999969482)=0x7FFF、0.5=0x4000、0.25=0x2000、0=0x0000、-0.5=0xC000、-1.0=0x8000を2進数とあわせて示し、右側に-1.0から0.999969482までの数直線を示している。

固定小数点では、実装よりも設計段階での判断が品質を左右します。スケールや型、丸め方法を適切に決定しておくことで、精度低下やオーバーフローを未然に防げます。

スケーリング係数の決め方

まず、扱う値の最大値・最小値と必要な分解能を整理し、それに基づいて倍率を決定します。倍率を大きくすると精度は向上しますが、表現できる範囲は狭くなります。そのため、実際のセンサデータや制御データを用いてダイナミックレンジを確認し、精度とオーバーフロー余裕のバランスを取ることが重要です。

Q形式の考え方

Q形式は、暗黙の2のべき乗スケールで小数点位置を固定する表現です。たとえばQ1.15(Q15)は、16bit符号付き(2の補数)整数のうち、最上位の1bitを符号ビットとし、残り15bitを小数部に割り当てます。表現可能範囲は -1.0~0.999969482…(=32767/32768)、分解能は2^-15です。

Q1.31(Q31)も同様に、32bit中1bitが符号、31bitが小数部で、範囲は-1.0~0.9999999995…(=(2^31-1)/2^31)、分解能は2^-31となります。

これらは一般的な信号処理ライブラリでも標準的なデータ形式として採用されています。ライブラリと同じ形式を利用することで、最適化済み関数を効率よく活用できます。この記事ではQ15=Q1.15、Q31=Q1.31の意味で用います(以降はQ15、Q31に統一)。

オーバーフローを考慮した型設計

乗算や積算では演算途中の値が大きくなるため、入力が16bitでも32bitや64bitへ一時的に拡張して計算することが基本です。また、各演算で取り得る最大値・最小値を事前に見積もることで、オーバーフローの発生を防止できます。

丸め誤差を小さくするには

固定小数点演算では、切り捨てを繰り返すと誤差が一方向へ蓄積することがあります。そのため、用途に応じて「最近傍丸め(例:四捨五入)」「切り捨て」「切り上げ」など適切な丸めの方法を選択し、バイアスを抑えることが重要です。切り捨ては実装が軽い一方、系統的なバイアスが出るため用途に注意が必要です。制御系では、このわずかな誤差が長時間の積算で大きな差となる場合もあります。

C言語での固定小数点演算の実装と確認ポイント

固定小数点演算では、演算そのものだけでなく、スケール管理や演算順序も精度に大きく影響します。ここでは、実装時に押さえておきたいポイントを紹介します。

加算・減算・乗算・除算の実装

加算・減算では、演算する値同士のスケールを必ず統一します。乗算では積が拡大するため、演算後にシフトなどで元のスケールへ戻します。除算では、必要に応じて被除数を左シフト(nビット左シフトする。nは小数部のビット数で、Q15であれば15)して値を拡大してから割ることで、小数部の精度(分解能)を維持します。ただし左シフトはオーバーフローしやすいため、事前に64bitへ拡張してからシフトします。中間計算は上位幅(32→64bit 等)を使い、最後に丸めや飽和をおこないます。

演算順序による精度の改善

演算順序を工夫すると精度低下を抑えられます。除算回数はできるだけ減らし、定数計算はコンパイル時に済ませます。また、途中で不要な丸めをおこなわず、最後にまとめて丸めることも有効です。

ライブラリの活用

マイコン向けに標準化された信号処理ライブラリや、半導体メーカが提供する固定小数点演算ライブラリには、Q15・Q31演算やDSP向け関数が用意されています。性能や信頼性を確保しやすいため、自作実装だけでなくライブラリの活用も検討しましょう。

デバッグ時の確認ポイント

浮動小数点演算との比較結果を基準に、代表値・異常値・境界値を用いたテストを実施します。最大値付近やゼロ近傍など誤差が生じやすい条件を重点的に確認することが重要です。

固定小数点演算における主な問題と対策

固定小数点演算では、速度と引き換えに精度や表現範囲に制約があります。設計段階からリスクを把握し、適切な対策を講じることが重要です。

オーバーフローと精度損失

演算結果が型の範囲を超えるとオーバーフローが発生し、丸めやシフトを繰り返すと精度も失われます。演算範囲を事前に見積もり、必要に応じて上位ビット幅の型を利用しましょう。用途によっては、オーバーフロー時にラップアラウンドさせず飽和(最大値/最小値へクランプ)する設計が有効です(制御量・フィルタ出力など)。

精度不足による制御性能低下

スケーリングが不適切だと、センサ分解能やPID制御の性能を十分に引き出せません。実データを用いて倍率を見直し、必要な分解能を確保することが重要です。

演算速度と処理負荷の最適化

64bit演算は処理負荷が高くなるため、必要な場面だけで使用します。シフト演算やコンパイラ最適化を活用し、速度と精度のバランスを取ることが重要です。

テストで確認すべき項目

最大値・最小値や異常値入力、長時間の積算結果を確認し、浮動小数点演算との差も評価します。境界条件で問題が発生しないことを確認しておきましょう。

固定小数点演算の設計と実践ノウハウ

品質の高い固定小数点演算を実現するには、実装技術だけでなく、チーム内で設計ルールを統一することも重要です。

設計時に決めておくべきルール

スケールや型定義はプロジェクト全体で統一し、Q形式や倍率をコメントで明記します。ルールを文書化することで、保守性やレビュー効率が向上します。

コードレビュー時の確認ポイント

型変換による情報欠落やオーバーフローの可能性、シフト量の妥当性を重点的に確認します。演算結果だけでなく、設計意図どおりのスケールになっているかも確認しましょう。

保守性を高める実装技術

typedefで固定小数点型を統一し、共通関数やインライン関数で演算処理を集約すると、修正漏れや実装ばらつきを防止できます。

浮動小数点演算との使い分け

リアルタイム性や省メモリを重視する処理には固定小数点演算、大きなダイナミックレンジや高精度演算には浮動小数点演算が適しています。用途に応じて両者を使い分けることが、性能と保守性を両立するポイントです。

組み込みソフトの世界 トップへ戻る