Main Content

Cette page s'applique à la version précédente. La page correspondante en anglais a été supprimée de la version actuelle.

Effectuer des opérations en arithmétique à virgule fixe

Cet exemple montre comment effectuer les opérations de base en arithmétique à virgule fixe.

Sauvegardez les états d’avertissement (warning states) avant de commencer.

warnstate = warning;

Addition et soustraction

Chaque fois que vous additionnez deux nombres à virgule fixe non signés, il est possible que vous ayez besoin d’un bit de report pour représenter correctement le résultat. Pour cette raison, lors de l’addition de deux nombres à B bits (avec la même mise à l’échelle), la valeur résultante possède un bit supplémentaire par rapport aux deux opérandes utilisés.

a = ufi(0.234375,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'

Avec les nombres en complément à deux signés, un scénario similaire se produit en raison de l'extension de signe nécessaire pour représenter correctement le résultat.

a = sfi(0.078125,4,6);
b = sfi(-0.125,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'

Si vous additionnez ou soustrayez deux nombres d’une précision différente, il faut d’abord aligner la séparation fractionnaire pour pouvoir effectuer l’opération. Le résultat est qu’il y a une différence de plus d’un bit entre le résultat de l’opération et les opérandes (en fonction de la distance qui sépare les séparations fractionnaires).

a = sfi(pi,16,13);
b = sfi(0.1,12,14);
c = a + b
c = 

    3.2416

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

Autres considérations sur l’addition et la soustraction

Remarque : le modèle de code suivant n’est pas recommandé. Comme les additions scalaires sont effectuées à chaque itération dans la boucle for, un bit est ajouté à la variable temp lors de chaque itération. Il en résulte qu’au lieu d’une croissance de bits de ceil(log2(Nadds)), la croissance des bits est égale à Nadds.

s = rng; rng('default');
b = sfi(4*rand(16,1)-2,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

Si, à la place, on utilise la commande sum, la croissance des bits est freinée comme attendu.

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

Multiplication

Généralement un produit en précision totale nécessite une longueur de mot égale à la somme des longueurs de mot des opérandes. Dans l’exemple suivant, notez que la longueur de mot du produit c est égale à la longueur de mot de a plus la longueur de mot de b. La longueur de la partie fractionnaire de c est aussi égale à la longueur de la partie fractionnaire de a plus la longueur de la partie fractionnaire de b.

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

    8.5397

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

Affectation

Lorsque vous affectez une valeur à virgule fixe dans une variable prédéfinie, cela peut impliquer une quantification. Dans de tels cas, la partie droite de l’expression est quantifiée en arrondissant au plus proche puis en saturant, si nécessaire, avant d’affecter à la partie gauche.

N = 10;
a = sfi(2*rand(N,1)-1,16,15);
b = sfi(2*rand(N,1)-1,16,15);
c = sfi(zeros(N,1),16,14);
for n = 1:N
    c(n) = a(n).*b(n);
end

Notez que, lorsque le produit a(n).*b(n) est calculé avec une précision totale, cela génère un résultat intermédiaire de longueur de mot 32 et de longueur de partie fractionnaire 30. Ce résultat est alors quantifié à une longueur de mot de 16 et une longueur de partie fractionnaire de 14, comme expliqué précédemment. La valeur quantifiée est ensuite affectée à l’élément c(n).

Quantification explicite des résultats

Souvent, il n’est pas souhaitable d’arrondir au plus proche ou de saturer lors de la quantification d’un résultat car cela entraîne de la logique/du calcul supplémentaire. Il n'est également pas souhaitable pour pouvoir effectuer la quantification d'avoir à affecter à une valeur côté gauche. Pour cela, vous pouvez utiliser QUANTIZE. Un cas courant est celui de la boucle de rétroaction. Si on n’introduit pas de quantification, une croissance de bits illimitée va se produire à mesure que l’on fournira davantage de données d’entrée.

a = sfi(0.1,16,18);
x = sfi(2*rand(128,1)-1,16,15);
y = sfi(zeros(size(x)),16,14);
for n = 1:length(x)
    z    = y(n);
    y(n) = x(n) - quantize(a.*z, true, 16, 14, 'Floor', 'Wrap');
end

Dans cet exemple, le produit a.*z est calculé avec une précision totale et est ensuite quantifié à une longueur de mot de 16 bits et une longueur de partie fractionnaire de 14. La quantification est réalisée en arrondissant au « plancher » (troncature) et en autorisant le wrapping en cas d'overflow. La quantification se produit malgré tout à l’affectation, car l’expression x(n) - quantize(a.*z, ...) produit un résultat intermédiaire de 18 bits alors que y est défini pour avoir 16 bits. Pour éliminer la quantification à l’affectation, vous pouvez introduire une quantification explicite supplémentaire comme présenté ci-dessous. L’avantage de ce procédé est d’éviter de recourir à la logique de l’arrondi au plus proche/de la saturation. Le résultat côté gauche ayant la même longueur de mot de 16 bits et la même longueur de partie fractionnaire de 14 que y(n), il n’est pas nécessaire de quantifier.

a = sfi(0.1,16,18);
x = sfi(2*rand(128,1)-1,16,15);
y = sfi(zeros(size(x)),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

Sommes à précision non totale

Il n’est pas toujours souhaitable d’effectuer des sommes à précision totale. Par exemple, la longueur de mot de 18 bits correspondant au résultat intermédiaire x(n) - quantize(...) ci-dessus peut aboutir à un code compliqué et inefficace, si un code C est généré. Il peut être plus intéressant de maintenir tous les résultats d’addition/de soustraction à 16 bits. Pour ce faire, vous pouvez utiliser les fonctions accumpos et accumneg.

a = sfi(0.1,16,18);
x = sfi(2*rand(128,1)-1,16,15);
y = sfi(zeros(size(x)),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

Modélisation des accumulateurs

accumpos et accumneg conviennent particulièrement à la modélisation des accumulateurs. Le comportement correspond à celui des opérateurs += et -= en C. Prenons l’exemple courant d’un filtre RIF dans lequel les coefficients et les données d’entrée sont représentés avec 16 bits. La multiplication s’effectue en précision totale, produisant 32 bits, et un accumulateur de 8 bits de garde, c’est-à-dire de 40 bits au total, est utilisé pour permettre jusqu’à 256 accumulations sans qu’il y ait possibilité d'overflow.

b = sfi(1/256*[1:128,128:-1:1],16); % Filter coefficients
x = sfi(2*rand(300,1)-1,16,15);     % Input data
z = sfi(zeros(256,1),16,15);        % Used to store the states
y = sfi(zeros(size(x)),40,31);      % Initialize Output data
for n = 1:length(x)
    acc = sfi(0,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

Arithmétique matricielle

Pour simplifier la syntaxe et raccourcir les temps de simulation, vous pouvez utiliser l’arithmétique matricielle. Dans l’exemple du filtre RIF, vous pouvez remplacer la boucle interne par un produit interne.

z = sfi(zeros(256,1),16,15); % Used to store the states
y = sfi(zeros(size(x)),40,31);
for n = 1:length(x)
    z(1) = x(n);
    y(n) = b*z;
    z(2:end) = z(1:end-1);
end

Le produit interne b*z est effectué en précision totale. Comme il s’agit d’une opération matricielle, la croissance des bits est due à la fois à la multiplication impliquée et à l’addition des produits résultants. C’est pourquoi la croissance des bits dépend de la longueur des opérandes. Étant donné que b et z ont une longueur de 256, cela représente une croissance de 8 bits due aux additions. C’est pour cela que le produit interne donne 32 + 8 = 40 bits (avec une longueur de la partie fractionnaire de 31). Puisqu’il s’agit du format dans lequel y est initialisé, aucune quantification ne se produit pendant l'affectation y(n) = b*z.

Si vous deviez effectuer un produit interne pour plus de 256 coefficients, la croissance des bits serait supérieure à 8 bits outre les 32 nécessaires pour le produit. Si vous n’aviez qu’un accumulateur 40 bits, vous pourriez modéliser le comportement soit en introduisant un quantificateur, comme dans y(n) = quantize(Q,b*z), soit en utilisant la fonction accumpos comme montré précédemment.

Modélisation d’un compteur

accumpos peut servir à modéliser un compteur simple qui « enveloppe » (wraps) naturellement après avoir atteint sa valeur maximale. Par exemple, vous pouvez modéliser un compteur sur 3 bits de la manière suivante.

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

Puisque le compteur sur 3 bits ramène naturellement par wrapping à 0 après avoir atteint 7, la valeur finale du compteur est mod(20,8) = 4.

Mathématiques avec d’autres types de données prédéfinis

FI * DOUBLE

Lors de la multiplication entre fi et double, le double est converti (par cast) en un fi ayant la même longueur de mot et le même signe que le fi, et une longueur de la partie fractionnaire dotée de la meilleure précision. Le résultat de l’opération est un 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 ou FI - DOUBLE

Lors de l’addition ou de la soustraction entre fi et double, le double est converti (par cast) en un fi ayant la même propriété numerictype que le fi. Le résultat de l’opération est un fi.

Ce comportement de fi + double a été modifié dans la version R2012b. Il est possible d’inactiver l’avertissement d’incompatibilité en saisissant la commande d’avertissement suivante.

warning off fixed:incompatibility:fi:behaviorChangeHeterogeneousMathOperationRules
a = fi(pi);
b = a + 1
b = 

    4.1416

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

Quelques différences entre MATLAB® et C

Notez qu'en C, le résultat d’une opération entre un type de données entier et un type de données double donne un double.

Cependant, dans MATLAB, le résultat d’une opération entre un type de données entier prédéfini et un type de données double est un entier. À cet égard, l’objet fi se comporte comme les types de données entiers prédéfinis dans MATLAB. Le résultat d’une opération entre un fi et un double est un fi.

FI * INT8

Lors d’opérations arithmétiques entre fi et l’un des types de données entiers prédéfinis [u]int[8,16,32], la longueur du mot et le signe de l’entier sont préservés. Le résultat de l’opération est un fi.

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

    6.2832

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

Restaurez les états d’avertissement (warning states).

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