Quand MATLAB et fprintf ne regardent pas dans la même direction…

La fonction fprintf de MATLAB est très utile pour contrôler l’écriture de données dans un fichier texte. Sa syntaxe est la suivante :

fprintf(fileid, format, A)

Le premier argument fileid est l’identifiant du fichier renvoyé par la fonction fopen. Le deuxième argument format gère le formatage de l’écriture. C’est l’argument le plus compliqué à maitriser et il faudra souvent se référer à la documentation pour savoir quel formatage utiliser. Le dernier argument A est le tableau de données à écrire dans le fichier.

Il y a une subtilité à connaitre, même pour des cas d’utilisation de fprintf relativement simple. C’est ce que je vais vous présenter maintenant.

Le problème

Ecrire dans un fichier texte le tableau suivant :

M = [1 2 ; 3 4 ; 5 6]

Soit :

M =

     1     2
     3     4
     5     6

Le contenu du fichier doit donc être le suivant :

1 2
3 4
5 6

Comme un fichier texte s’écrit ligne après ligne (en séparant chaque ligne avec un retour à la ligne), on serait tenté d’écrire le code suivant :

fid = fopen('test.txt', 'wt');
fprintf(fid, '%d %d\n', M);
fclose(fid);

concise-writer-calvin

%d signifie que la valeur à écrire sera un entier. On met deux fois ce format car le tableau a deux colonnes. Puis on demande à fprintf de passer à la ligne avec \n. fprintf répète ensuite ce motif tant qu’il y a des valeurs à écrire. En faisant cela, on ne tient pas compte du nombre de lignes du tableau. Ce qui est très pratique si ce nombre n’est pas constant entre deux appels à fprintf.

En exécutant le code ci-dessus, le fichier texte contient :

1 3
5 2
4 6

Aïe ! Cela ne correspond pas du tout au tableau initial. Les valeurs semblent mélangées.

Les raisons

Le code précédent est faux car il y a une différence fondamentale entre MATLAB et la fonction fprintf.

Le stockage en mémoire des tableaux par MATLAB est particulier. Les valeurs sont stockées dans un vecteur où elles sont toutes contiguës (il n’y a pas d’élément vide entre elles). MATLAB les stocke colonne par colonne (1ère colonne, 2ème colonne, 3ème colonne…). Lire à ce sujet : Stockage des matrices en mémoire.

Le tableau suivant :

M =

     1     2
     3     4
     5     6

est donc stocké comme ceci en mémoire :

     1 3 5 2 4 6

Pourquoi ce choix me direz-vous ? Eh bien parce que MATLAB était initialement écrit en Fortran. Et que les tableaux sont définis colonne par colonne dans ce langage. Donc fprintf prend ces valeurs dans cet ordre et il est alors normal de trouver le 1 et le 3 sur la première ligne du fichier, suivi du 5 et du 2 sur la deuxième ligne et pour finir, du 4 et du 6 sur la dernière.

D’accord, mais les concepteurs de MATLAB auraient facilement pu prendre ceci en compte et, sans tenir compte de l’ordre de stockage par MATLAB, créer une fonction fprintf respectant un passage du tableau sous sa forme naturelle.

Oui mais voila, en 1984, MATLAB a été réécris en langage C. La fonction fprintf de MATLAB repose donc directement sur la fonction homonyme en C (voir les références en bas de la documentation).

Et alors, pourquoi vous parler de Fortran et de C ? Eh bien parce que, en langage C, les tableaux sont définis ligne par ligne.

Si MATLAB utilisait la convention de stockage du C, le code suivant fonctionnerait donc :

fprintf(fid, '%d %d\n', M);

Or ce n’est pas le cas. La fonction fprintf n’a donc pas un comportement naturel pour l’écriture des tableaux de MATLAB.

Les solutions

La première solution consiste à prendre en compte ce problème dès la conception du code et à créer le tableau suivant :

M = [1 3 5 ; 2 4 6];

Soit :

M =

     1     3     5
     2     4     6

Il n’est malheureusement pas toujours envisageable de créer le tableau de cette manière.

La deuxième solution consiste alors à transposer le tableau passé à fprintf. C’est à dire à permuter les lignes et les colonnes du tableau :

M = [1 2 ; 3 4 ; 5 6];
M = M.';

Le tableau devient donc :

M =

     1     3     5
     2     4     6

On peut ensuite faire :

fprintf(fid, '%d %d\n', M);

Ou directement avec le tableau d’origine :

fprintf(fid, '%d %d\n', M.');

La fonction fprintf écrira donc d’abord les valeurs 1 et 2 sur une ligne du fichier, puis 3 et 4 sur une autre, pour finir avec 5 et 6 sur la dernière.

Conclusion

Vous savez maintenant comment écrire correctement un tableau dans un fichier texte avec un seul appel à fprintf depuis MATLAB.

fprintf reste une fonction difficile à manipuler. N’hésitez pas à me poser des questions ou à me soumettre des suggestions à propos de cette fonction en commentaire. Je pourrai toujours faire d’autres billets au sujet de cette fonction.

3 réflexions au sujet de « Quand MATLAB et fprintf ne regardent pas dans la même direction… »

  1. Bilel

    bonjour ,
    mon problème est le suivant :
    j’ai utilisé fprintf sur un vecteur colonne qui devient un vecteur de caractères je veux le rendre de nouveau un vecteur colonne j’ai utilisé str2mat elle ne donne rien :

    >> A=str2mat(fprintf('%10.1f \n',Cl))

    A =

    Ũ
    Répondre
    1. Avatar de Jerome BriotJerome Briot Auteur de l’article

      Attention, fprintf renvoi le nombre de caractères écrits et non pas la chaine de caractères obtenue après conversion :

      >> Cl = rand(3,1);
      >> n = fprintf('%.4f\n', Cl)
      0.9572
      0.4854
      0.8003

      n =

          21

      Il faut utiliser sprintf :

      >> str = sprintf('%.4f\n', Cl)

      str =

      0.9572
      0.4854
      0.8003

      Pour effectuer la conversion inverse, on peut utiliser sscanf :

      >> Cl = sscanf(str, '%f')

      Cl =

          0.9572
          0.4854
          0.8003
      Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>