Main Content

固定小数点演算の実行

この例では、基本的な固定小数点算術演算の実行方法を説明します。

開始する前にすべての警告の現在の状態を保存します。

warnstate = warning;

加算と減算

2 つの符号なし固定小数点数を加算するときは、結果を正確に表すためにキャリー ビットが必要となる場合があります。このため、同じスケーリングをもつ 2 つの B ビット数を加算すると、結果の値は、使用される 2 つのオペランドと比べてビットが多くなります。

a = fi(0.234375,0,4,6);
c = a + a
c = 
    0.4688

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Unsigned
            WordLength: 5
        FractionLength: 6
a.bin
ans = 
'1111'
c.bin
ans = 
'11110'

符号付きの 2 の補数を使用すると、結果を正確に表すために必要な符号拡張のため、同じような状況が発生します。

a = fi(0.078125,1,4,6);
b = fi(-0.125,1,4,6);
c = a + b
c = 
   -0.0469

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 5
        FractionLength: 6
a.bin
ans = 
'0101'
b.bin
ans = 
'1000'
c.bin
ans = 
'11101'

精度が異なる 2 つの数値を加算または減算する場合は、最初に基数点を揃えてから演算を行う必要があります。その結果、基数点がどれだけ遠いかに応じて、演算の結果とオペランドには 1 つを超えるビットの違いがあります。

a = fi(pi,1,16,13);
b = fi(0.1,1,12,14);
c = a + b
c = 
    3.2416

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 18
        FractionLength: 14

加算と減算に関するその他の考慮事項

以下のパターンは "推奨されません"。スカラー加算は for ループ内の各反復で実行されるので、各反復でビットが temp に追加されます。結果として、ceil(log2(Nadds)) のビット成長率ではなく、ビット成長率は Nadds に等しくなります。

s = rng; rng('default');
b = fi(4*rand(16,1)-2,1,32,30);
rng(s); % restore RNG state
Nadds = length(b) - 1;
temp = b(1);
for n = 1:Nadds
    temp = temp + b(n+1); % temp has 15 more bits than b
end

sum コマンドを代わりに使用すると、ビット成長率は制限されます。

c = sum(b) % c has 4 more bits than b
c = 
    7.0059

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 36
        FractionLength: 30

乗算

一般に、完全精度の積には、オペランドの語長の合計と同じ語長が必要です。以下の例では、積 c の語長は、a の語長に b の語長を加算した長さと等しくなります。c の小数部の長さも、a の小数部の長さに b の小数部の長さを加算した長さと等しくなります。

a = fi(pi,1,20);
b = fi(exp(1),1,16);
c = a*b
c = 
    8.5397

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 36
        FractionLength: 30

代入

事前定義された変数に固定小数点値を代入するときは、量子化が行われる場合があります。このような場合は、必要に応じて左辺へ代入される前に、最も近い正の整数方向に丸められて飽和することで、式の右辺が量子化されます。

N = 10;

a = fi(2*rand(N,1)-1,1,16,15);
b = fi(2*rand(N,1)-1,1,16,15);
c = fi(zeros(N,1),1,16,14);

for n = 1:N
    c(n) = a(n).*b(n);
end

a(n).*b(n) を完全精度で計算すると、32 の語長と 30 の小数部の長さをもつ中間結果が生成されます。その結果は、16 ビットの語長と 14 ビットの小数部の長さに量子化されます。量子化された値は、要素 c(n) に代入されます。

結果の明確な量子化

追加のロジックと計算が必要となる場合は、結果を量子化するときの最も近い正の整数方向への丸めまたは飽和は不適切であることがあります。また、左辺値に割り当てて量子化を実行することが不適切な場合もあります。このような場合は、関数 quantize を使用できます。一般的な例はフィードバック ループです。量子化を行わない場合は、指定される入力データが増えると、ビット成長率に制限がなくなります。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);

for n = 1:length(x)
    z    = y(n);
    y(n) = x(n) - quantize(a.*z,true,16,14,'Floor','Wrap');
end

この例では、積 a.*z を完全精度で計算し、次に 16 ビットの語長と 14 ビットの小数部の長さに量子化します。負方向への丸め (切り捨て) とオーバーフローが発生した場合のラッピングによって量子化が行われます。量子化は割り当て時にも発生します。これは、式 x(n) - quantize(a.*z,...) が 18 ビットの中間結果を生成し、y が 16 ビットとなるように定義されているためです。

割り当て時の量子化を回避するために、最も近い正の整数方向への丸めまたは飽和ロジックが使用されないように明確な追加量子化を取り入れることができます。左辺の結果は y(n) と同じ 16 ビットの語長と 14 ビットの小数部の長さをもつので、量子化が不要です。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);
T = numerictype(true,16,14);


for n = 1:length(x)
    z    = y(n);
    y(n) = quantize(x(n),T,'Floor','Wrap') - ...
           quantize(a.*z,T,'Floor','Wrap');
end

完全精度ではない和

完全精度の和は適切であるとは限らず、複雑で効率の悪い C コードが生成される可能性があります。たとえば、上記の例の中間結果 x(n) - quantize(...) では、語長は 18 ビットです。ただし、16 ビットに対する加算と減算の結果をすべて保持するためには適切である場合があります。16 ビットに対する加算と減算の結果を保持するには、関数 accumpos および関数 accumneg を使用できます。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);

T = numerictype(true, 16, 14);

for n = 1:length(x)
    z    = y(n);
    y(n) = quantize(x(n),T);                 % defaults: 'Floor','Wrap'
    y(n) = accumneg(y(n),quantize(a.*z, T)); % defaults: 'Floor','Wrap'
end

アキュムレータのモデル化

関数 accumpos および関数 accumneg を使用して、アキュムレータをモデル化できます。accumposaccumneg の動作はそれぞれ C の演算子 += および -= に対応しています。一般的な例は、係数および入力データを 16 ビットで表現する FIR フィルターです。乗算は完全精度で実行され、32 ビットと 8 ガード ビットのアキュムレータが生成されます。合計 40 ビットが使用されるので、オーバーフローを発生させずに最大 256 の累積が可能になります。

b = fi(1/256*[1:128,128:-1:1],1,16); % Filter coefficients
x = fi(2*rand(300,1)-1,1,16,15);     % Input data
z = fi(zeros(256,1),1,16,15);        % Used to store the states
y = fi(zeros(size(x)),1,40,31);      % Initialize Output data

for n = 1:length(x)
    acc = fi(0,1,40,31); % Reset accumulator
    z(1) = x(n);        % Load input sample
    for k = 1:length(b)
        acc = accumpos(acc,b(k).*z(k)); % Multiply and accumulate
    end
    z(2:end) = z(1:end-1); % Update states
    y(n) = acc;            % Assign output
end

行列の算術演算

構文を簡略化してシミュレーション時間を短縮するために、行列の算術演算を使用できます。FIR フィルターの例では、内側のループを内積に置き換えることができます。

z = fi(zeros(256,1),1,16,15); % Used to store the states
y = fi(zeros(size(x)),1,40,31);

for n = 1:length(x)
    z(1) = x(n);
    y(n) = b*z;
    z(2:end) = z(1:end-1);
end

内積 b*z は完全精度で実行されます。これは行列演算なので、ビット成長率は含まれる乗算と乗算結果の加算の両方に起因します。したがって、ビット成長率はオペランドの長さによって異なります。この例では、b および z の長さは 256 なので、加算により 8 ビット成長率となります。内積は 32 + 8 = 40 ビット、小数部の長さは 31 ビットとなります。y はこの形式に初期化されたため、代入 y(n) = b*z では量子化が起こりません。

256 より多い係数に対して内積を行わなければならなかった場合、ビット成長率は 8 ビットを上回り、乗算に必要な 32 ビットを超えます。40 ビットのアキュムレータのみをもっていた場合は、y(n) = quantize(Q,b*z) のような量子化器を取り入れるか、または関数 accumpos を使用することで、動作をモデル化できます。

カウンターのモデル化

accumpos を使用して、最大値に達するとラッピングする簡単なカウンターをモデル化できます。たとえば、以下のように 3 ビットのカウンターをモデル化できます。

c = fi(0,0,3,0);
Ncounts = 20; % Number of times to count
for n = 1:Ncounts
    c = accumpos(c,1);
end

3 ビットのカウンターは 7 に達するとゼロに戻るので、カウンターの最終値は mod(20,8) = 4 です。

他の組み込みデータ型を使用した演算

C 言語では、整数データ型と double データ型の間での演算の結果は double に変換されます。ただし、MATLAB® では、組み込み整数データ型と double データ型の間の演算の結果は、整数になります。この点で、fi オブジェクトは MATLAB の組み込み整数データ型のように動作します。fidouble の間の演算の結果は fi になります。

fi * double

fidouble の間で乗算を行うと、double は、fi と同じ語長と符号属性および最高精度の小数部の長さをもつ fi にキャストされます。演算の結果は fi になります。

a = fi(pi);
b = 0.5 * a
b = 
    1.5708

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 32
        FractionLength: 28

fi + double または fi - double

fidouble の間で加算または減算を行うと、double は fi と同じ numerictype をもつ fi にキャストされます。演算の結果は fi になります。

a = fi(pi);
b = a + 1
b = 
    4.1416

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 17
        FractionLength: 13

fi * int8

fi と組み込み整数データ型 [u]int[8,16,32,64] のうちの 1 つとの間で演算を行うと、整数の語長と符号属性が保持されます。演算の結果は fi になります。

a = fi(pi);
b = int8(2) * a
b = 
    6.2832

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 24
        FractionLength: 13

警告状態を復元します。

warning(warnstate);
%#ok<*NASGU,*NOPTS>

参考

|