Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

MATLAB の浮動小数点アルゴリズムの固定小数点への手動変換

この例では、浮動小数点アルゴリズムを固定小数点に変換し、そのアルゴリズムに C コードを生成する方法を説明します。この例では、以下のベスト プラクティスを使用します。

  • テスト ファイルからアルゴリズムを分離

  • インストルメンテーションおよびコード生成用にアルゴリズムを準備

  • データ型の管理とビット成長の抑制

  • データ定義のテーブルの作成により、データ型定義をアルゴリズム コードから分離

ベスト プラクティスの一覧については、手動による固定小数点の変換のベスト プラクティスを参照してください。

テスト ファイルからのアルゴリズムの分離

ベクトルの要素を合計する MATLAB® 関数 mysum を記述します。

function y = mysum(x)
  y = 0;
  for n = 1:length(x)
    y = y + x(n);
  end
end

アルゴリズム部分を固定小数点に変換する必要があるだけなので、コア プロセッシングを行うアルゴリズムをテストファイルから分離するには、コードを構造化する方法がより効率的です。

テスト スクリプトの記述

テスト ファイル内で入力を作成し、アルゴリズムを呼び出して結果をプロットします。

  1. MATLAB スクリプトの mysum_test を記述し、double データ型を使用してアルゴリズムの動作を確認します。

    n = 10;
    rng default
    x = 2*rand(n,1)-1;
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected

    rng default は関数 rand で使用される乱数発生器の設定を既定値にするため、MATLAB を再起動した場合も同じ乱数が生成されます。

  2. テスト スクリプトを実行します。

    mysum_test
    err =
    
         0

    mysum を使用して取得した結果は MATLAB 関数 sum を使用して得た結果に一致します。

詳細は、テスト ファイルの作成を参照してください。

インストルメンテーションとコード生成のためのアルゴリズムの準備

アルゴリズム内の関数シグネチャの後ろに、%#codegen コンパイル命令を追加すると、アルゴリズムをインストルメント化してその C コードを生成することを示しています。この命令を追加することで、MATLAB コード アナライザーが、インストルメンテーションおよびコード生成中にエラーが発生する可能性のある違反の診断と修正をサポートできるようになります。

function y = mysum(x) %#codegen
  y = 0;  
  for n = 1:length(x)
    y = y + x(n);
  end
end

このアルゴリズムでは、エディター ウィンドウの右上隅のコード アナライザー インジケーターは緑のままで、問題が検出されなかったことを示します。

Screenshot of mysum function with code analyzer indicating there are no issues.

詳細は、コード高速化またはコード生成のためのアルゴリズムの準備を参照してください。

元のアルゴリズムの C コードの生成

元のアルゴリズムの C コードを生成して、アルゴリズムがコード生成に適していることを確認し、浮動小数点 C コードを確認します。関数 codegen (MATLAB Coder) (MATLAB Coder™ が必要) を使用して C ライブラリを生成します。

  1. テスト スクリプトの最後に次の行を追加し、mysum の C コードを生成します。

    codegen mysum -args {x} -config:lib -report

  2. テスト スクリプトを再度実行します。

    MATLAB Coder は関数 mysum の C コードを生成し、コード生成レポートへのリンクを提供します。

  3. リンクをクリックしてコード生成レポートを開き、生成された mysum の C コードを表示します。

    /* Function Definitions */
    double mysum(const double x[10])
    {
      double y;
      int n;
      y = 0.0;
      for (n = 0; n < 10; n++) {
        y += x[n];
      }
      return y;
    }
    

    C では浮動小数点のインデックスは使用できないので、ループ カウンター n は自動的に整数型として宣言されます。n を固定小数点に変換する必要はありません。

    入力 x および出力 y は double として宣言されます。

データ型の管理とビット成長の抑制

型の不一致を確認するための single を使用したアルゴリズムのテスト

  1. x のデータ型が single になるようにテスト ファイルを変更します。

    n = 10;
    rng default
    x = single(2*rand(n,1)-1);
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    codegen mysum -args {x} -config:lib -report

  2. テスト スクリプトを再度実行します。

    mysum_test
    err =
    
      -4.4703e-08
    
    ??? This assignment writes a 'single' value into a 'double' type. 
    Code generation does not support changing types through assignment. 
    Check preceding assignments or input type specifications for type 
    mismatches.

    コード生成は失敗し、行 y = y + x(n); でデータ型の不一致がレポートされます。

  3. エラーを表示するには、レポートを開きます。

    レポート内で、行 y = y + x(n) の代入式の左辺の y が赤で強調表示され、エラーがあることを示します。y は double として宣言されているにもかかわらず single に代入されていることが問題です。y + x(n) は double と single の合計であり、single になります。レポート内の変数と式にカーソルを置くと、それらの型についての情報が表示されます。ここでは、式 y + x(n) は single であるとわかります。

    Screenshot of report with cursor hovering over the expression y+x(n).

  4. 型の不一致を修正するには、要素の合計に添字による代入を使用するようにアルゴリズムを更新します。y = y + x(n)y(:) = y + x(n) に変更します。

    function y = mysum(x) %#codegen
      y = 0;
      for n = 1:length(x)
        y(:) = y + x(n);
      end
    end
    

    添字による代入を使用すると、固定小数点数を追加した場合の既定の動作であるビット成長を回避することもできます。詳細については、ビット成長率を参照してください。ビット成長の回避は、コード全体で固定小数点型を維持するために重要です。詳細については、ビット成長率の抑制を参照してください。

  5. C コードを再度生成し、コード生成レポートを開きます。C コード内で、型の不一致が解消されるように結果が double にキャストされています。

インストルメント化された MEX のビルド

関数 buildInstrumentedMex を使用してアルゴリズムをインストルメント化し、名前付き変数と中間変数の最小値と最大値のログを記録します。関数 showInstrumentationResults を使用し、ログに記録された値に基づいて固定小数点型を推奨します。後に、これらの推奨された固定小数点型を使用してアルゴリズムをテストします。

  1. テスト スクリプトを更新します。

    1. n を宣言した後、buildInstrumentedMex mySum —args {zeros(n,1)} -histogram を追加します。

    2. x を double に戻します。x = single(2*rand(n,1)-1);x = 2*rand(n,1)-1; に置き換えます。

    3. 元のアルゴリズムを呼び出す代わりに、生成済みの MEX 関数を呼び出します。y = mysum(x)y=mysum_mex(x) に変更します。

    4. MEX 関数を呼び出した後、showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL を追加します。-defaultDT numerictype(1,16) -proposeFL フラグは 16 ビット語長の小数部の長さを推奨することを示します。

      更新されたテスト スクリプトは次のようになります。

      %% Build instrumented mex
      n = 10;
      
      buildInstrumentedMex mysum -args {zeros(n,1)} -histogram
      
      %% Test inputs
      rng default
      x = 2*rand(n,1)-1;
      
      % Algorithm
      y = mysum_mex(x);
      
      % Verify results
      
      showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
      y_expected = sum(double(x));
      
      err = double(y) - y_expected
      
      %% Generate C code
      
      codegen mysum -args {x} -config:lib -report
      

  2. テスト スクリプトを再度実行します。

    関数 showInstrumentationResults によってデータ型が推奨され、レポートが開いて結果が表示されます。

  3. レポート内で [変数] タブをクリックします。showInstrumentationResults により y に 13 および x に 15 の小数部の長さが推奨されています。

    Screenshot of Variables tab containing column for Proposed FL.

レポートでは、以下の作業を実行できます。

  • 入力 x および出力 y のシミュレーションの最小値と最大値の表示。

  • x および y の推奨データ型の表示。

  • コード内のすべての変数、中間結果および式の情報の表示。

    この情報を表示するには、カーソルをレポート内の変数または式の上に置きます。

  • x および y のヒストグラム データを確認することで、現在のデータ型に基づいて範囲外の値や精度の低い値を特定しやすくなります。

    特定の変数のヒストグラムを表示するには、その変数のヒストグラム アイコン をクリックします。

データ型定義のアルゴリズム コードからの分離

各データ型の動作を調べるためにアルゴリズムを手動で変更する代わりに、データ型の定義をアルゴリズムから分離します。

mysum が、入力および出力データのデータ型を定義する構造体である入力パラメーター T を取るように変更します。y が最初に定義されるときに、関数 cast を構文のように cast(x,'like',y) として使用し、x を望ましいデータ型にキャストします。

function y = mysum(x,T) %#codegen
  y = cast(0,'like',T.y);
  for n = 1:length(x)
    y(:) = y + x(n);
  end
end

データ型定義のテーブルの作成

アルゴリズムのテストに使用するさまざまなデータ型を定義する関数 mytypes を記述します。データ型のテーブルに double、single およびスケーリングされた double データ型、さらに先に推奨された固定小数点データ型を含めます。アルゴリズムを固定小数点に変換する前に、以下を実行することをお勧めします。

  • データ型定義テーブルと double を使用するアルゴリズム間との接続をテストします。

  • single を使用してアルゴリズムをテストし、データ型の不一致や他の問題を見つけます。

  • スケーリングされた double を使用してアルゴリズムを実行し、オーバーフローを確認します。

function T = mytypes(dt)
  switch dt
    case 'double'
      T.x = double([]);
      T.y = double([]);
    case 'single'
      T.x = single([]);
      T.y = single([]);
    case 'fixed'
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,13);
    case 'scaled'
      T.x = fi([],true,16,15,...
           'DataType','ScaledDouble');
      T.y = fi([],true,16,13,...
           'DataType','ScaledDouble');
  end
end

詳細は、データ型定義のアルゴリズムからの分離を参照してください。

データ型テーブルを使用するためのテスト スクリプトの更新

データ型テーブルを使用するようにテスト スクリプト mysum_test を更新します。

  1. 最初の実行時に、テーブルと double を使用するアルゴリズムの接続を確認します。n を宣言する前に、T = mytypes('double'); を追加します。

  2. buildInstrumentedMex の呼び出しを更新し、データ型テーブル内で指定された T.x のデータ型を使用します。buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram

  3. x をキャストして、テーブル内で指定された T.x のデータ型を使用します。 x = cast(2*rand(n,1)-1,'like',T.x);

  4. T を渡して MEX 関数を呼び出します。y = mysum_mex(x,T);

  5. T を渡して codegen を呼び出します。codegen mysum -args {x,T} -config:lib -report

    更新されたテスト スクリプトは次のようになります。

    %% Build instrumented mex
    T = mytypes('double');
    
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    
    % Algorithm
    y = mysum_mex(x,T);
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  6. テスト スクリプトを実行し、リンクをクリックしてコード生成レポートを開きます。

    生成された C コードは元のアルゴリズム用に生成されたコードと同一になります。型の指定には変数 T が使用され、これらの型はコード生成時に定数となるので、T は実行時には使用されず、生成コード内には現れません。

固定小数点コードの生成

前に推奨された固定小数点型を使用するようにテスト スクリプトを更新し、生成された C コードを表示します。

  1. 固定小数点型を使用するようにテスト スクリプトを更新します。T = mytypes('double');T = mytypes('fixed'); に置き換え、スクリプトを保存します。

  2. テスト スクリプトを実行して生成された C コードを表示します。

    このバージョンの C コードはオーバーフローの処理が多く含まれており、あまり効率的ではありません。次のステップではオーバーフローを回避するためにデータ型を最適化します。

データ型の最適化

スケーリングされた double を使用したオーバーフローの検出

スケーリングされた double は浮動小数点数と固定小数点数のハイブリッドです。Fixed-Point Designer™ は、スケーリング、符号および語長の情報を維持したまま double として保存します。すべての算術は倍精度で実行されるので、オーバーフローの発生を確認できます。

  1. スケーリングされた double を使用するようにテスト スクリプトを更新します。T = mytypes('fixed');T = mytypes('scaled'); に置き換えます。

  2. テスト スクリプトを再度実行します。

    テストはスケーリングされた double を使用して実行され、レポートが表示されます。オーバーフローは検出されません。

    ここまでは、乱数入力を使用してテスト スクリプトを実行しました。これは、テストがアルゴリズムの操作範囲全体で実行されていない可能性を意味します。

  3. 入力の全範囲を検索します。

    range(T.x)
    -1.000000000000000   0.999969482421875
    
              DataTypeMode: Fixed-point: binary point scaling
                Signedness: Signed
                WordLength: 16
            FractionLength: 15

  4. スクリプトを更新して負のエッジ ケースをテストします。元の乱数入力および全範囲をテストする入力で mysum_mex を実行して結果を集計します。

    %% Build instrumented mex
    T = mytypes('scaled');
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    y = mysum_mex(x,T);
     % Run once with this set of inputs
    y_expected = sum(double(x));
    err = double(y) - y_expected
    
    % Run again with this set of inputs. The logs will aggregate.
    x = -ones(n,1,'like',T.x);
    y = mysum_mex(x,T); 
    y_expected = sum(double(x));
    err = double(y) - y_expected 
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  5. テスト スクリプトを再度実行します。

    テストが実行され、y は固定小数点データ型の範囲でオーバーフローします。showInstrumentationResultsy について新しい小数部の長さ 11 を推奨します。

    Screenshot of instrumentation results showing a Proposed FL of 11 for y.

  6. y について新たに推奨された型と共にスケーリングされた double を使用するようにテスト スクリプトを更新します。myTypes.m 内で、'scaled' ケースに対して T.y = fi([],true,16,11,'DataType','ScaledDouble') とします。

  7. テスト スクリプトを再実行します。

    これでオーバーフローがなくなります。

推奨された固定小数点型のコードの生成

推奨された固定小数点型を使用するようにデータ型テーブルを更新してコードを生成します。

  1. myTypes.m 内で、'fixed' ケースに対して T.y = fi([],true,16,11) とします。

  2. T = mytypes('fixed'); を使用するようにテスト スクリプト mysum_test を更新します。

  3. テスト スクリプトを実行し、[レポートの表示] リンクをクリックして生成された C コードを表示します。

    short mysum(const short x[10])
    {
      short y;
      int n;
      int i;
      int i1;
      int i2;
      int i3;
      y = 0;
      for (n = 0; n < 10; n++) {
        i = y << 4;
        i1 = x[n];
        if ((i & 1048576) != 0) {
          i2 = i | -1048576;
        } else {
          i2 = i & 1048575;
       }
       
        if ((i1 & 1048576) != 0) {
         i3 = i1 | -1048576;
        } else {
          i3 = i1 & 1048575;
        }
    
      i = i2 + i3;
      if ((i & 1048576) != 0) {
        i |= -1048576;
      } else {
        i &= 1048575;
      }
    
      i = (i + 8) >> 4;
      if (i > 32767) {
        i = 32767;
      } else {
          if (i < -32768) {
            i = -32768;
          }
        }
    
       y = (short)i;
      }
      return y;
    }

    既定では、fi の演算ではオーバーフローで飽和と最も近い正の整数方向への丸めが使用され、結果として非効率的なコードになります。

fimath 設定の変更

生成コードをより効率的にするには、C コードの生成により適切な固定小数点演算 (fimath) の設定を使用します。つまり、オーバーフロー時のラップと負方向の丸めを使用します。

  1. myTypes.m 内で 'fixed2' ケースを追加します。

     case 'fixed2'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,16,11,F);
    

    ヒント

    fimath プロパティを手動で入力する代わりに、MATLAB エディターの [fimath を挿入] オプションを使用できます。詳細は、GUI での fimath オブジェクト コンストラクターの作成を参照してください。

  2. 'fixed2' を使用するようにテスト スクリプトを更新し、スクリプトを実行して生成された C コードを表示します。

    short mysum(const short x[10])
    {
     short y;
     int n;
     y = 0;
     for (n = 0; n < 10; n++) {
       y = (short)(((y << 4) + x[n]) >> 4);
     }
    
      return y;
    }

    生成されたコードはより効率的になりますが、yx に整合するようにシフトされ、4 ビットの精度が失われます。

  3. この桁落ちを修正するには、y の語長を 32 ビットに更新し、15 ビットの精度を維持して x に整合させます。

    myTypes.m 内で 'fixed32' ケースを追加します。

     case 'fixed32'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,32,15,F);
    

  4. 'fixed32' を使用するようにテスト スクリプトを更新し、スクリプトを実行してコードを再生成します。

    これで生成コードが非常に効率的になります。

    int mysum(const short x[10])
    {
      int y;
      int n;
      y = 0;
      for (n = 0; n < 10; n++) {
        y += x[n];
      }
     
      return y;
    }

詳細は、アルゴリズムの最適化を参照してください。