Main Content

La traduction de cette page n'est pas à jour. Cliquez ici pour voir la dernière version en anglais.

Conversion manuelle d’un algorithme MATLAB virgule flottante en virgule fixe

Cet exemple montre comment convertir un algorithme virgule flottante en algorithme virgule fixe et générer ensuite un code C pour cet algorithme. L’exemple utilise les bonnes pratiques suivantes :

  • Séparer votre algorithme du fichier test.

  • Préparer votre algorithme en vue de l'instrumentation et de la génération de code.

  • Gérer les types de données et contrôler la croissance des bits.

  • Séparer les définitions des types de données du code algorithmique en créant une table de définitions des données.

Pour une liste complète des bonnes pratiques, voir Manual Fixed-Point Conversion Best Practices.

Séparer votre algorithme du fichier test

Écrivez une fonction MATLAB®, mysum, qui effectue la somme des éléments d’un vecteur.

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

Puisque qu’il vous suffit de convertir la portion algorithmique en virgule fixe, il est plus efficace de structurer votre code pour que l’algorithme, dans lequel vous procédez au traitement principal, soit séparé du fichier test.

Écrire un script de test

Dans le fichier de test, créez vos entrées, appelez l’algorithme, et tracez les résultats.

  1. Écrivez un script MATLAB, mysum_test, qui vérifie le comportement de votre algorithme en utilisant les types de données doubles.

    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 règle les paramètres du générateur de nombres aléatoires utilisé par la fonction random à leurs valeurs par défaut afin qu’il produise les mêmes nombres aléatoires que si vous redémarriez MATLAB.

  2. Exécutez le script de test.

    mysum_test
    err =
    
         0

    Les résultats obtenus en utilisant mysum correspondent à ceux obtenus en utilisant la fonction sum MATLAB.

Pour plus d’informations, consultez Create a Test File.

Préparer votre algorithme en vue de l'instrumentation et de la génération de code

Dans votre algorithme, après la signature de la fonction, ajoutez la directive de compilation %#codegen pour indiquer que vous souhaitez procéder à l’instrumentation de l’algorithme et générer un code C pour lui. L’ajout de cette directive stipule à l’analyseur de code MATLAB de vous aider à diagnostiquer et à remédier aux violations qui entraîneraient des erreurs lors de l’instrumentation et de la génération de code.

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

Pour cet algorithme, le témoin de l’analyseur de code, en haut à droite de la fenêtre d’édition, reste vert, ce qui indique qu’il n’a détecté aucune erreur.

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

Pour plus d’informations, consultez Prepare Your Algorithm for Code Acceleration or Code Generation.

Générer un code C pour votre algorithme original

Générez un code C pour l’algorithme original afin de vérifier que l’algorithme convient pour la génération de code, et afin de voir le code C à virgule flottante. Utilisez la fonction codegen (MATLAB Coder) (MATLAB Coder™ est requis), pour générer une bibliothèque C.

  1. Ajoutez la ligne suivante à l’extrémité de votre script de test afin de générer le code C pour mysum.

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

  2. Exécutez à nouveau le script de test.

    MATLAB Coder génère un code C pour la fonction mysum et fournit un lien pour le rapport relatif à la génération du code.

  3. En cliquant sur ce lien, vous ouvrez le rapport sur la génération du code et visualisez le code C créé pour mysum.

    /* 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;
     }

    Comme le langage C n’autorise pas les indices à virgule flottante, le compteur de boucle, n, est automatiquement déclaré comme un type entier. Vous n’avez pas besoin de convertir n en virgule fixe.

    L’entrée x et la sortie y sont déclarées comme doubles.

Gérer les types de données et contrôler la croissance des bits

Testez votre algorithme avec des « singles » pour identifier d’éventuelles non-concordances de types

  1. Modifiez votre fichier test pour que le type de données de x soit 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. Exécutez à nouveau le script de test.

    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.

    La génération de code échoue, et une non-concordance de type de données est signalée à la ligne y = y + x(n);.

  3. Ouvrez le rapport pour voir l’erreur.

    À la ligne y = y + x(n), le rapport affiche en rouge le y sur la gauche de l’affectation pour indiquer qu’il y a une erreur. Le problème est que y est déclaré comme un double alors qu’il est assigné à un single. y + x(n) est la somme d’un double et d’un single ce qui est un single. Placez votre curseur au-dessus des variables et des expressions du rapport pour obtenir des informations sur leurs types. Vous voyez ici que l’expression y + x(n) est un single.

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

  4. Pour remédier à la non-concordance de type, mettez votre algorithme à jour pour utiliser l’affectation indicée pour la somme des éléments. Changez y = y + x(n) en y(:) = y + x(n).

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

    En utilisant l’affectation indicée, vous évitez également la croissance des bits, qui est le comportement par défaut lorsque vous ajoutez des nombres à virgule fixe. Pour plus d’informations, consultez Croissance des bits. Il est important d’éviter la croissance des bits afin de pouvoir maintenir vos types à virgule fixe dans l’ensemble de votre code. Pour plus d’informations, consultez Contrôle de la croissance des bits.

  5. Générez à nouveau le code C et ouvrez le rapport de génération de code. Dans le code C, le résultat est maintenant converti en double pour remédier à la non-concordance de type.

Construire un fichier Mex instrumenté

Utilisez la fonction buildInstrumentedMex pour instrumenter votre algorithme afin d'enregistrer les valeurs minimale et maximale de toutes les variables nommées et intermédiaires. Utilisez la fonction showInstrumentationResults pour proposer des types de données à virgule fixe en fonction des valeurs enregistrées. Par la suite, vous utiliserez ces types à virgule fixe proposés pour tester votre algorithme.

  1. Mettez à jour le script de test :

    1. Après avoir déclaré n, ajoutez buildInstrumentedMex mySum —args {zeros(n,1)} -histogram.

    2. Changez x pour qu’il soit à nouveau un double. Remplacez x = single(2*rand(n,1)-1); par x = 2*rand(n,1)-1;

    3. Au lieu d’appeler l’algorithme original, appelez la fonction MEX générée. Changez y = mysum(x) en y=mysum_mex(x).

    4. Après avoir appelé la fonction MEX, ajoutez showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL. Les drapeaux -defaultDT numerictype(1,16) -proposeFL indiquent que vous voulez proposer des longueurs de partie fractionnaire pour une longueur de mot de 16 bits.

      Voici un script de test actualisé.

      %% 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. Exécutez à nouveau le script de test.

    La fonction showInstrumentationResults propose des types de données et ouvre un rapport pour afficher les résultats.

  3. Dans le rapport, cliquez sur l’onglet Variables. showInstrumentationResults propose une longueur de partie fractionnaire de 13 pour y et de 15 pour x.

    Screenshot of Variables tab containing column for Proposed FL.

Dans ce rapport, vous pouvez :

  • Voir la simulation des valeurs minimale et maximale pour l’entrée x et la sortie y.

  • Voir les types de données proposés pour x et y.

  • Voir les informations relatives à l’ensemble des variables, les résultats intermédiaires et les expressions de votre code.

    Pour voir ces informations s’afficher, placez votre curseur au-dessus des variables ou des expressions du rapport.

  • Affichez les données d’histogramme pour x et y afin de vous aider à identifier les valeurs qui sortent de la plage ou dont la précision est inférieure par rapport au type de données courant.

    Pour visualiser l’histogramme d’une variable particulière, cliquez sur l’icône de son histogramme, .

Séparer les définitions des types de données du code de l'algorithme

Plutôt que de modifier l’algorithme manuellement pour examiner le comportement de chaque type de données, séparez les définitions des types de données de l'algorithme.

Modifiez mysum de manière à ce qu’il utilise un paramètre d’entrée, T, qui est une structure définissant les types de données pour les données d’entrée et de sortie. Lors de la définition initiale de y, utilisez la syntaxe de la fonction cast avec like — cast(x,'like',y) — pour convertir (par cast) x dans le type de données souhaité.

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

Créer une table des définitions des types de données

Écrivez une fonction, mytypes, pour définir les différents types de données que vous souhaitez utiliser pour tester votre algorithme. Dans votre tableau de types de données, incluez les doubles, les singles et les types de données doubles mises à l’échelle ainsi que les types de données à virgule fixe proposés précédemment. Avant de convertir votre algorithme en virgule fixe, la bonne pratique consiste à :

  • Tester la connexion entre le tableau de définition des types de données et votre algorithme en utilisant des doubles.

  • Tester votre algorithme avec des singles pour identifier d’éventuelles non-concordance de types de données ou d’autres problèmes.

  • Exécuter l’algorithme en utilisant des doubles mis à l’échelle pour identifier d’éventuels overflows.

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

Pour plus d’informations, consultez Separate Data Type Definitions from Algorithm.

Mettre à jour le script de test pour utiliser la table des types

Mettez à jour le script de test, mysum_test, pour utiliser la table des types.

  1. Pour le premier essai, vérifiez la connexion entre la table et l’algorithme en utilisant des doubles. Avant de déclarer n, ajoutez T = mytypes('double');

  2. Mettez à jour l’appel de buildInstrumentedMex pour utiliser le type de T.x spécifié dans le tableau des types de données : buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram

  3. Convertissez par cast x pour utiliser le type de T.x spécifié dans la table : x = cast(2*rand(n,1)-1,'like',T.x);

  4. Appelez la fonction MEX en passant T : y = mysum_mex(x,T);

  5. Appelez codegen en passant T : codegen mysum -args {x,T} -config:lib -report

    Voici le script de test actualisé.

    %% 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. Exécutez le script de test et cliquez sur le lien pour ouvrir le rapport sur la génération du code.

    Le code C généré est le même que le code généré pour l’algorithme original. Étant donné que la variable T est utilisée pour spécifier les types et que ces types sont constants au moment de la génération du code ; T n’est pas utilisé au moment de l’exécution et n'apparaît pas dans le code généré.

Générer un code en virgule fixe

Mettez à jour le script de test pour utiliser les types à virgule fixe proposés précédemment et afficher le code C généré.

  1. Mettez à jour le script de test pour utiliser les types à virgule fixe. Remplacez T = mytypes('double'); par T = mytypes('fixed');, puis sauvegardez le script.

  2. Exécutez le script de test et affichez le code C généré.

    Cette version de code C n’est pas très efficace ; elle implique de nombreux traitements d'overflows. L’étape suivante consiste à optimiser les types de données pour éviter les overflows.

Optimiser les types de données

Utilisez les doubles mis à l'échelle pour détecter les overflows

Les doubles mis à l'échelle sont une forme hybride entre les nombres à virgule flottante et les nombres à virgule fixe. Fixed-Point Designer™ les stocke en tant que doubles en retenant les informations de mise à l’échelle, de signe et de longueur de mot. Toute l’arithmétique étant réalisée en double précision, vous pouvez voir les overflows qui se produisent.

  1. Mettez à jour le script de test pour utiliser les doubles mis à l’échelle. Remplacez T = mytypes('fixed'); par T = mytypes('scaled');

  2. Exécutez à nouveau le script de test.

    Le test s’exécute en utilisant des doubles mis à l’échelle et affiche le rapport. Aucun overflow n’a été détecté.

    Jusqu’ici, vous avez exécuté le script de test en utilisant des entrées aléatoires, ce qui signifie qu’il est peu probable que le texte ait mis en œuvre la totalité de la plage opérationnelle de l'algorithme.

  3. Trouvez toute la plage des valeurs de l’entrée.

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

  4. Mettez à jour le script pour tester le cas de contour négatif. Exécutez mysum_mex avec l’entrée aléatoire originale et avec une entrée qui teste toute la plage des valeurs et agrège les résultats.

    %% 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. Exécutez à nouveau le script de test.

    Le test s’exécute et y dépasse la plage du type de données à virgule fixe. showInstrumentationResults propose une nouvelle longueur de partie fractionnaire de 11 pour y.

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

  6. Mettez à jour le script de test pour utiliser les doubles mis à l’échelle avec le nouveau type proposé pour y. Dans myTypes.m, pour le cas 'scaled', T.y = fi([],true,16,11,'DataType','ScaledDouble')

  7. Exécutez le script de test à nouveau.

    Il n’y a maintenant plus d'overflow.

Générer un code pour le type à virgule fixe proposé

Mettez à jour la table des types de données pour utiliser le type à virgule fixe proposé et générez le code.

  1. Dans myTypes.m, pour le cas 'fixed', T.y = fi([],true,16,11)

  2. Mettez à jour le script de test, mysum_test, pour utiliser T = mytypes('fixed');

  3. Exécutez le script de test puis cliquez sur le lien « View Report » pour visualiser le code C généré.

    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;
    }

    Par défaut, l’arithmétique de fi utilise la saturation pour les overflows et l’arrondi au plus proche, ce qui donne un code inefficace.

Modifier les paramètres fimath

Pour rendre le code généré plus efficace, utilisez les paramètres des mathématiques à virgule fixe (fimath), qui sont plus appropriés pour la génération de codes C : wrap pour les overflows et arrondi « plancher ».

  1. Dans myTypes.m, ajoutez un cas '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);
    

    Conseil

    Au lieu de saisir manuellement les propriétés fimath, vous pouvez utiliser l’option Insert fimath de l’éditeur MATLAB. Pour plus d’informations, consultez Building fimath Object Constructors in a GUI.

  2. Mettez à jour le script de test pour utiliser 'fixed2', exécutez le script, puis visualisez le code C généré.

    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;
    }

    Le code généré est plus efficace, mais y est décalé pour s’aligner avec x et perd 4 bits de précision.

  3. Pour remédier à cette perte de précision, mettez à jour la longueur de mot de y à 32 bits et conservez 15 bits de précision pour l’alignement avec x.

    Dans myTypes.m, ajoutez un cas '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. Mettez à jour le script de test pour utiliser 'fixed32' et exécutez le script pour générer le code à nouveau.

    Le code généré est maintenant très efficace.

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

Pour plus d’informations, consultez Optimize Your Algorithm.