OVH Cloud OVH Cloud

Récupération des infos dans un fichier binaire C3D

22 réponses
Avatar
Michael
Bonsoir à tous,

pour un programme que je suis en train de développer, je dois extraire
des informations d'un fichier C3D écrit en binaire, et qui contient des
renseignements sur le résultat d'une capture de mouvement.

Le souci, c'est que je n'ai jamais eu à faire ce genre de lecture, et que
je sais pas comment m'y prendre.

Pour commencer, je voudrais juste pouvoir extraire les infos de l'entête
du fichier.
Dans la doc (http://www.c3d.org/HTML/default.htm), il y est dit que ce
fichier est composé de multiples blocs de 512 bytes.

//
All C3D files contain a minimum of three sections of information:
-----------------------------------------------------------------
A single, 512 byte, header section
-----------------------------------------------------------------
A parameter section consisting of one, or more, 512-byte blocks.
-----------------------------------------------------------------
3D point/analog data section consisting of one, or more, 512-byte blocks.
//

Cette page me donne les différentes composantes de cet entête:
http://www.c3d.org/HTML/description.htm

On m'a filé un code en C qui lit cet entête, mais j'y pipe rien du tout,
je vous la donne...

#define RECORD 512

typedef struct S_C3D {
int nbr_block_parameter;
int processor;
char **nom_block_parameter;
} C3D;

C3D c3d;

typedef struct matrice {
double **Mat;
} MATRICE;

typedef struct {
short cleffich; // w0 Clef du fichier
short nbmarqs; // w1 Nombre de marqueurs
short nbanalog; // w2 Nombre de donnees analogiques par image
video
short premimage; // w3 Premiere image
short dernimage; // w4 Derniere image
short maxinterpol; // w5 Interpolation maximum (nombre d'images)
float echelle; // w6-7 Facteur de conversion des donnees video
stockees sous forme d'entiers
short debutdonnees; // w8 Enregistrement de debut des donnees 1
enregistrement = 256 * word; 1 word = 16 bits
short freqanal; // w9 frequence d'echantillonage
analogique/donnees video
float freqvid; // w10-11 frequence d'echantillonage video
short x1[137]; // w12-w148 inutilise
short clef; // w149 mot clef (12345 octal typical value)
short nbevnttps; // w150 nb d'evenements temps definis
short x2; // w151 Number of defined time events
float fmtevnttps[18]; // w152-w187 dates evenements temporels (max
9-,pas 18!)
short fmtevntswitch[10];// w188-w197 byte switch de l evenement (0=on,1
=off)
char labelevnt[9][8]; // w198-w233 label de l evenement sur 4
caracteres
short x3[22]; // w234-w255 inutilise
}ENTETEC3D;

typedef union {
long lg;
float flt;
int in[2];
char ch[4];
}trans;

int LgLabel,NbTraj;
char *dim_label;
char **p_label; // Labels des points
trans tr;
short Nbimage;
char TempChar;

// Implémentation
float fltdecpc (char byt[4]) // fonction donnee par Oxford pour
transformer DEC en FLOAT .Recopiee integralement donc no comment !
{
long mantis;
short exp;
float flt;
float *f;

switch (proc) {
case 0:
f = (float *) byt;
flt = *f;
break;

case 1:
mantis = (byt[2] & 0xff) + ((byt[3] & 0xffl) << 8) + (((byt[0] &
0x7fl) | 0x80) << 16);
exp = (byt[1] & 0x7f) * 2 + ((byt[0] & 0x80) ? 1 : 0);
if (byt[1] & 0x80) mantis = -mantis;
flt = (mantis || exp) ? ((float) ldexp (mantis, (exp - 128 - 24)))
: 0;
break;
}
return (flt);
}


void litentete (char *nomfich)
{
char H[sizeof(ENTETEC3D)];
ENTETEC3D *header;
FILE *fich;
int i;

int n, G, m, n2, G2, d;
char *GroupName=NULL;
char *GroupDescription=NULL;
char *ParamName=NULL;
int *Dimensions=NULL;
short offset2, offset1;

if ((fich = OuvreFichier (nomfich, "rb", 0))!=NULL) {
header = (ENTETEC3D *)H;

for (i=0; i<sizeof(ENTETEC3D); i++) H[i] = fgetc(fich);

tr.flt = header->echelle;
header->echelle = fltdecpc(tr.ch);
tr.flt = header->freqvid;
header->freqvid = fltdecpc(tr.ch);
Nbimage = header->dernimage - header->premimage + 1;

// Lecture des paramètres pour récuperer les noms des marqueurs
correspondants à chaque trajectoire*/
fread (&TempChar, sizeof(char), 1, fich); // Byte 1
fread (&TempChar, sizeof(char), 1, fich); // Byte 1
fread (&TempChar, sizeof(char), 1, fich); // Byte 1
fread (&TempChar, sizeof(char), 1, fich); // Byte 1

// header
// n
fread (&TempChar, sizeof(char), 1, fich);
n = (int)TempChar;

while (n!=0) {
// G
fread (&TempChar, sizeof(char), 1, fich);
G = (int)TempChar;

// Group Name
GroupName = calloc (n+1, sizeof(*GroupName));
for (i=0;i<n;i++)
fread ((GroupName+i), sizeof(char), 1, fich);

// offset
fread (&offset1, sizeof(short), 1, fich);

// m
fread (&TempChar, sizeof(char), 1, fich);
m = (int)TempChar;

if (m==0) fseek(fich,(offset1-3),SEEK_CUR);
else {
GroupDescription = calloc (m, sizeof(*GroupDescription)); //
Group Description
for (i=0;i<(m);i++)
fread ((GroupDescription+i), sizeof(char), 1, fich);
free (GroupDescription);
}

// Paramètres
// n2
fread (&TempChar, sizeof(char), 1, fich);
n2 = (int) TempChar;

// G2
fread (&TempChar, sizeof(char), 1, fich);
G2 = (int) TempChar;

while (G2 == -G) {
// Param Name
ParamName = calloc (n2+1, sizeof(*ParamName));
for (i=0; i<n2; i++)
fread (&ParamName[i], sizeof(ParamName[i]), 1, fich);

// offset2
fread (&offset2, sizeof(short), 1, fich);

if ((strcmp(GroupName, "POINT")==0) &&
(strcmp(ParamName, "LABELS")==0)) {
// T
fread (&TempChar, sizeof(char), 1, fich);

// d
fread (&TempChar, sizeof(char), 1, fich);
d = (int) TempChar;

// Dimensions
Dimensions = calloc (d, sizeof(*Dimensions));
for (i=0;i<d;i++) {
fread (&TempChar, sizeof(char), 1, fich);
Dimensions[i] = (int)TempChar;
}

// lecture des labels
LgLabel = Dimensions[0];
NbTraj = Dimensions[1];
free (Dimensions);
fseek (fich, LgLabel*NbTraj, SEEK_CUR); // on passe les labels

// m2
fread(&TempChar, sizeof(char), 1, fich);
fseek(fich, (int)TempChar, SEEK_CUR); // on passe m2

fread (&TempChar, sizeof(char), 1, fich); // n2
n2 = (int)TempChar;

fread (&TempChar, sizeof(char), 1, fich); // G2
G2 = (int)TempChar;

}
else {
free (ParamName);

// Passage au parametre suivant
fseek(fich,(offset2-2),SEEK_CUR);

fread (&TempChar, sizeof(char), 1, fich); // n2
n2 = (int)TempChar;

fread (&TempChar, sizeof(char), 1, fich); // G2
G2 = (int)TempChar;
}
}

fseek (fich,(-2),SEEK_CUR);
free (GroupName);

fread(&TempChar, sizeof(char), 1, fich); // n
n = (int) TempChar;
}
FermeFichier (&fich);
}
}


Comment je peux lire correctement cet entête en bon C++?

Merci d'avance

Mike

10 réponses

1 2 3
Avatar
Michael
"kanze" wrote in news:1140691900.559197.28400
@z34g2000cwc.googlegroups.com:

float
IntelDataHandler::readFloat(
istream& source ) const
{
unsigned char tmp[ 4 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 4) ) {
// Erreur...
}
float result
= ((tmp[ 2 ] & 0x7F) << 16) | (tmp[ 1 ] << 8) | tmp[ 0 ] ;
int dummy ;
result = ldexp( frexp( result, &dummy ),
tmp[ 2 ] >> 7 | tmp[ 3 ] << 1 ) ;
return (tmp[ 3 ] & 0x80) != 0
? - result
: result ;
}


J'ai une erreur à l'éxécution sur la dernière ligne:

Débordement en virgule flottante

sur :

return (tmp[ 3 ] & 0x80) != 0 ? - result : result;

De où ça vient?

Question subsidiaire: avez-vous des liens qui pourraient me permettre de
comprendre le code ci-dessus, qui pour moi est du chinois le plus complet?

Avatar
kanze
Michael wrote:
class DataHandler
{
public:
virtual ~DataHandler() {}
virtual short readShort( istream& source ) const = 0 ;
virtual float readFloat( istream& source ) const = 0 ;
} ;

Ce sont les instances concrètes de cette classe où ça se corse.
Pour un IntelDataHandler, par exemple, ça serait quelque chose
comme :

short
IntelDataHandler::readShort(
istream& source ) const
{
unsigned char tmp[ 2 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 2 ) ) {
// Erreur...
}
// Je suppose ici que la machine cible utilise une
// représentation complément à deux, et gère la
// conversion unsigned -> signed de la façon classique.
return (tmp[ 1 ] << 8) | tmp[ 0 ] ;
}

float
IntelDataHandler::readFloat(
istream& source ) const
{
unsigned char tmp[ 4 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 4) ) {
// Erreur...
}
float result
= ((tmp[ 2 ] & 0x7F) << 16) | (tmp[ 1 ] << 8) | tmp[ 0 ] ;
int dummy ;
result = ldexp( frexp( result, &dummy ),
tmp[ 2 ] >> 7 | tmp[ 3 ] << 1 ) ;
return (tmp[ 3 ] & 0x80) != 0
? - result
: result ;
}


Quelles seraient les implémentations des classes permettant de
lire des short ou des floats pour des représentations DEC ou
MIPS?


Les short dans la représentation DEC, c'est identique aux short
Intel -- je crois (sans être 100% certain) que pour le MIPS, il
faut inverser le 1 et le 0 dans le return.

Quant aux floats... Il faudrait trouver de la documentation
quelque part sur leur format sur ces machines. Je ne le connais
pas. (J'ai lu quelque part que le format DEC -- c-à-d le format
PDP-11, je suppose, parce que DEC a fabriqué d'autres machines,
avec d'autres formats aussi -- est très proche du IEEE qui
utilise Intel. Reste à savoir aussi si l'ordre des octets est
1234, comme chez Intel, ou 3412, comme étaient les long, si mes
souvenirs sont exacts.)

Je ne connais absolument rien à ce pan de la programmation...


C'est un peu spécial:-). Je le connais moi-même surtout parce
que je viens du côté hardware, et que j'ai travaillé à ce niveau
il y a une vingtaine d'années.

En gros, en ce qui concerne les entiers, je crois qu'on ne prend
pas trop de risque en supposant une représentation en complément
à deux. Et c'est bien écrit que les entiers dans ton fichier
sont tous à 16 bits, c-à-d deux octets (ici, « octet » en
anglais, et non « byte »). Alors, la seule question, c'est
l'ordre des octets -- est-ce que c'est le premier ou le deuxième
qui est le poid fort.

En ce qui concerne les flottants, c'est bien plus complexe. En
gros, un flottant comporte trois champs : une mantisse, un
exposant et un bit de signe. Alors, pour commencer, il faut
savoir où se trouve les trois champs dans les quatre octets, et
la représentation de chaque champs. (Et en passant, j'ai écrit
le code ci-dessus à la volée, et en le regardant, je vois tout
de suite au moins deux erreurs.) Pour le format IEEE, on a un
exposant de 8 bits, en excès 126, une mantisse de 24 bits, dont
le bit de poids fort est toujours 1, et n'est pas représenté, et
une signe d'un bit. Chez Intel, on trouve la signe sur le bit de
poid fort du dernier octet, l'exposant sur les sept autres bits
de cet octet, plus le bit de poid fort de l'avant dernier octet,
et la mantisse sur les autres bits. Il s'agit alors de les
extraire, de les normaliser (ce que j'ai oublié dans le code
ci-dessus), et d'en recomposer le flottant dans le format natif,
à l'aide des fonctions du genre ldexp et frexp. (Si on ne tiens
pas à la portabilité, et qu'on sait bien ce qu'on fait, on peut
aussi composer le float directement en mémoire soi-même.)

Si j'ajoute les normalisations au code que j'ai posté, ça
ressemblerait à :

IntelDataHandler::readFloat(
istream& source ) const
{
unsigned char tmp[ 4 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 4) ) {
// Erreur...
}
float result
= (((tmp[ 2 ] & 0x7F) << 16 | 0x800000)) | (tmp[ 1 ] << 8)
| tmp[ 0 ] ;
int dummy ;
result = ldexp( frexp( result, &dummy ),
(tmp[ 2 ] >> 7 | tmp[ 3 ] << 1) - 126 ) ;
return (tmp[ 3 ] & 0x80) != 0
? - result
: result ;
}

Aussi, il faudrait faire un cas à part pour 0 -- si l'exposant
est 0, il ne faut pas faire le « | 0x800000 » sur la mantisse,
ni soustraire quoique ce soit de l'exposant.

Et finalement, j'ai ignoré complétement la question des valeurs
NaN et Inf. Encore des cas spéciaux, cette fois-ci si l'exposant
est 0xFF.

Dans la pratique, j'ai résolu le problème pour le format XDR,
qui correspond au format Intel sauf en ce qui concerne l'ordre
des octets. Vu que le format XDR comporte aussi des entiers
non-signé de quatre octets, j'ai pû travailler directement
là-dessus, mais ce n'est pas dit que ce soit une mauvaise idée
dans ton cas -- c'est peut-être un peu plus transparent ce qui
se passe. Enfin, ce que ça donne, c'est :

ixdrstream&
ixdrstream::operator>>(
GB_uint32_t& dest )
{
ByteGetter source( *this ) ;
GB_uint32_t tmp = source.get() << 24 ;
tmp |= source.get() << 16 ;
tmp |= source.get() << 8 ;
tmp |= source.get() ;
if ( *this ) {
dest = tmp ;
}
return *this ;
}

ixdrstream&
ixdrstream::operator>>(
float& dest )
{
GB_uint32_t tmp ;
operator>>( tmp ) ;
if ( *this ) {
float f = 0.0 ;
if ( (tmp & 0x7FFFFFFF) != 0 ) {
f = ldexp( ((tmp & 0x007FFFFF) | 0x00800000),
(int)((tmp & 0x7F800000) >> 23) - 126 - 24 )
;
}
if ( (tmp & 0x80000000) != 0 ) {
f = -f ;
}
dest = f ;
}
return *this ;
}

(Ici non plus, je n'ai pas encore traité tous les cas spéciaux,
genre Inf ou NaN. En revanche, le code est testé pour des cas
« normaux ».)

ByteGetter, c'est une classe assez simple qui s'occupe de gérer
les erreurs s'il n'y a pas d'octet disponible.

Pour Intel, il faudrait surtout inverser l'ordre des octets dans
l'extraction du uint32_t. Sinon, la reste doit marcher.

Note aussi que vue que je connais l'exposant après la conversion
de la mantisse en flottant, je triche sur l'offset de l'exposant
que je passe à ldexp, plutôt que d'appeler frexp.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Michael wrote:
"kanze" wrote in news:1140691900.559197.28400
@z34g2000cwc.googlegroups.com:

float
IntelDataHandler::readFloat(
istream& source ) const
{
unsigned char tmp[ 4 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 4) ) {
// Erreur...
}
float result
= ((tmp[ 2 ] & 0x7F) << 16) | (tmp[ 1 ] << 8) | tmp[ 0 ] ;
int dummy ;
result = ldexp( frexp( result, &dummy ),
tmp[ 2 ] >> 7 | tmp[ 3 ] << 1 ) ;
return (tmp[ 3 ] & 0x80) != 0
? - result
: result ;
}


J'ai une erreur à l'éxécution sur la dernière ligne:

Débordement en virgule flottante

sur :

return (tmp[ 3 ] & 0x80) != 0 ? - result : result;

De où ça vient?


Du fait que j'ai oublié à « normaliser » l'exposant et la
mantisse dans ce code. Voir mon autre réponse.

Question subsidiaire: avez-vous des liens qui pourraient me
permettre de comprendre le code ci-dessus, qui pour moi est du
chinois le plus complet?


Oui et non? Avec quel aspect est-ce que tu as des problèmes :

-- Avec les opérateurs >>, <<, et | ? Ce sont des opérateurs
binaires de C++ (et de C). Il doit bien y avoir des pages
quelque part qui les expliquent en détail, mais je ne sais
pas où -- en venant du côté hardware, il me semble tellement
naturels que je n'ai jamais cherché une explication.

-- Avec les fonctions frexp() et ldexp() ? Ce sont des
fonctions standard de C (et donc de C++). Il faut les
chercher dans la documentation de C.

-- Avec le format des virgules flottants en général ? Il y a
beaucoup de liens à
http://cch.loria.fr/documentation/IEEE754/ (malheureusement,
beaucoup d'entre eux n'ont pas l'air de marcher -- et le
firewall ici me réfuse accès à d'autres !). D'après mes
souvenirs, www.validgh.com était un lien essentiel, mais
c'est celui dont le firewall ici me réfuse l'accès.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Michael
"kanze" wrote in news:1141029270.916616.10520
@u72g2000cwu.googlegroups.com:

IntelDataHandler::readFloat(
istream& source ) const
{
unsigned char tmp[ 4 ] ;
if ( ! source.read( reinterpret_cast< char* >( tmp ), 4) ) {
// Erreur...
}
float result
= (((tmp[ 2 ] & 0x7F) << 16 | 0x800000)) | (tmp[ 1 ] << 8)
| tmp[ 0 ] ;
int dummy ;
result = ldexp( frexp( result, &dummy ),
(tmp[ 2 ] >> 7 | tmp[ 3 ] << 1) - 126 ) ;
return (tmp[ 3 ] & 0x80) != 0
? - result
: result ;
}


Il y a encore une erreur chez moi, avec tmp: { 0xCD, 0xCC, 0xCC, 0xBD }
depuis le fichier...

J'ai l'exception Débordement en virgule flottante...

La valeur qui devrait être retournée est -0,10

Par contre avec tmp: { 0x0, 0x0, 0x70, 0x42 } j'ai bien la bonne valeur, à
savoir 60

Je vais jeter un oeil sur de la doc sur internet. Je commence à piger
quelques trucs, mais c'est vraiment pas facile...

En tout cas, merci pour ton aide!

Avatar
Michael
Les short dans la représentation DEC, c'est identique aux short
Intel -- je crois (sans être 100% certain) que pour le MIPS, il
faut inverser le 1 et le 0 dans le return.

Quant aux floats... Il faudrait trouver de la documentation
quelque part sur leur format sur ces machines. Je ne le connais
pas. (J'ai lu quelque part que le format DEC -- c-à-d le format
PDP-11, je suppose, parce que DEC a fabriqué d'autres machines,
avec d'autres formats aussi -- est très proche du IEEE qui
utilise Intel. Reste à savoir aussi si l'ordre des octets est
1234, comme chez Intel, ou 3412, comme étaient les long, si mes
souvenirs sont exacts.)


J'ai une fonction C qui permet de convertir un DEC en float...

Peut-être (et comment?) est-ce que je pourrais l'utiliser?

float fltdecpc (char byt[4]) // fonction donnee par Oxford pour
transformer DEC en FLOAT .Recopiee integralement donc no comment !
{
long mantis;
short exp;
float flt;
float *f;

switch (proc) {
case 0:
f = (float *) byt;
flt = *f;
break;

case 1:
mantis = (byt[2] & 0xff) + ((byt[3] & 0xffl) << 8) + (((byt[0] &
0x7fl) | 0x80) << 16);
exp = (byt[1] & 0x7f) * 2 + ((byt[0] & 0x80) ? 1 : 0);
if (byt[1] & 0x80) mantis = -mantis;
flt = (mantis || exp) ? ((float) ldexp (mantis, (exp - 128 - 24)))
: 0;
break;
}
return (flt);
}

Avatar
Michael
J'ai tenté d'utiliser le code C que j'ai donné avant, et ça marche pour
les représentations Intel et DEC, que ce soit pour les short ou les
float...

J'ai ça du coup:

//-----------------------------------------------------------------------
--
short DECDataHandler::readShort(std::ifstream & source ) const
{
unsigned char tmp[ 2 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),2))
throw Exception("Impossible de lire le fichier");

return (tmp[ 1 ] << 8) | tmp[ 0 ] ;
}

//-----------------------------------------------------------------------
--
float DECDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

long mantis = (tmp[2] & 0xff) + ((tmp[3] & 0xffl) << 8) + (((tmp[0] &
0x7fl) | 0x80) << 16);
short exp = (tmp[1] & 0x7f) * 2 + ((tmp[0] & 0x80) ? 1 : 0);
if (tmp[1] & 0x80)
mantis = -mantis;

float flt = (mantis || exp) ? ((float) ldexp (mantis, (exp - 128 - 24)))
: 0;

return flt;
}

et

short IntelDataHandler::readShort(std::ifstream & source ) const
{
unsigned char tmp[ 2 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),2))
throw Exception("Impossible de lire le fichier");

// Je suppose ici que la machine cible utilise une
// représentation complément à deux, et gère la
// conversion unsigned -> signed de la façon classique.
return (tmp[ 1 ] << 8) | tmp[ 0 ] ;
}

//-----------------------------------------------------------------------
//! Retourne un float
float IntelDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

float * f = (float *)tmp;
float flt = *f;

//float result = (((tmp[ 2 ] & 0x7F) << 16 | 0x800000)) | (tmp[ 1 ] << 8)
| tmp[ 0 ] ;
//int dummy;
//result = ldexp( frexp( result, &dummy ), (tmp[ 2 ] >> 7 | tmp[ 3 ] <<
1) - 126 );
//return (tmp[ 3 ] & 0x80) != 0
// ? - result
// : result ;

return flt;
}

Et j'ai bien les bonnes valeurs...
Avatar
kanze
Michael wrote:
J'ai tenté d'utiliser le code C que j'ai donné avant, et ça
marche pour les représentations Intel et DEC, que ce soit pour
les short ou les float...


Dans tous les cas ?

Je sais que le format des short est identique entre Intel et
DEC. Je crois aussi que le format des float (mais non des
doubles) est très semblable aussi. En revanche, je ne crois pas
que le format DEC supporte les dénormalisés, et je ne sais pas
en ce qui concerne les Inf et les NaN. Mais il se peut que ce ne
soit pas important -- que de telles valeurs ne t'intéressent
pas. (Aussi, si DEC ne supporte pas les dénormalisés, ça veut
dire simplement qu'ils n'apparaîtront pas.)

En ce qui concerne le troisième format : il y a tout à parier
qu'il suffit d'inverser les octets par rapport au format Intel.

J'ai ça du coup:

//-----------------------------------------------------------------------
--
short DECDataHandler::readShort(std::ifstream & source ) const
{
unsigned char tmp[ 2 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),2))
throw Exception("Impossible de lire le fichier");

return (tmp[ 1 ] << 8) | tmp[ 0 ] ;
}

//-----------------------------------------------------------------------
--
float DECDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

long mantis = (tmp[2] & 0xff) + ((tmp[3] & 0xffl) << 8) + (((tmp[0] &
0x7fl) | 0x80) << 16);
short exp = (tmp[1] & 0x7f) * 2 + ((tmp[0] & 0x80) ? 1 : 0);
if (tmp[1] & 0x80)
mantis = -mantis;

float flt = (mantis || exp) ? ((float) ldexp (mantis, (exp - 128 - 24)))
: 0;

return flt;
}


En somme, c'est à peu près IEEE sur un long avec l'ordre des
octets 3412. Et l'exposant est excès 128, plutôt que excès 126.
On pourrait prèsqu'en faire une classe templatée.

et

short IntelDataHandler::readShort(std::ifstream & source ) const
{
unsigned char tmp[ 2 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),2))
throw Exception("Impossible de lire le fichier");

// Je suppose ici que la machine cible utilise une
// représentation complément à deux, et gère la
// conversion unsigned -> signed de la façon classique.
return (tmp[ 1 ] << 8) | tmp[ 0 ] ;
}

//-----------------------------------------------------------------------
//! Retourne un float
float IntelDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

float * f = (float *)tmp;
float flt = *f;


Attention ici. Ce que tu as fait dépend beaucoup de la machine.
(J'en conclus, d'ailleurs, que tu travaille sur un Intel. Ça ne
marcherait pas sur un Sparc.)

//float result = (((tmp[ 2 ] & 0x7F) << 16 | 0x800000)) | (tmp[ 1 ] << 8)
| tmp[ 0 ] ;
//int dummy;
//result = ldexp( frexp( result, &dummy ), (tmp[ 2 ] >> 7 | tmp[ 3 ] <<
1) - 126 );
//return (tmp[ 3 ] & 0x80) != 0
// ? - result
// : result ;

return flt;
}

Et j'ai bien les bonnes valeurs...


Essaie d'utiliser la même stratégie pour les flottants Intel que
pour les flottants DEC, puis inverser l'ordre des octets, et tu
dois pouvoir traiter le troisième format aussi.

Au fond, je crois qu'effectivement, on s'en tire très bien avec
des templates :

template< int byteOrder, int exponentExcess >
struct FloatConverter
{
int i0() const { return byteOrder / 1000 % 10 -
1 ; }
int i1() const { return byteOrder / 100 % 10 -
1 ; }
int i2() const { return byteOrder / 10 % 10 - 1
; }
int i3() const { return byteOrder % 10 - 1 ; }

float operator()( unsigned char bytes[ 4 ] )
const
{
float result = 0.0 ;
int exp = ((bytes[ i3() ] & 0x7F) << 1)
| (bytes[ i2() ] >> 7) ;
uint_least32_t mantisse = bytes[ i0() ]
| (bytes[ i1() ] << 8)
| ((bytes[ i2() ] & 0x7F) << 16) ;
if ( exp == 0 && mantisse != 0 ) {
// dénormalisé...
} else if ( exp == 0xFF ) {
// NaN, Inf...
} else {
result = ldexp( mantisse | 0x800000, exp -
exponentExcess - 24) ;
}
return (bytes[ i3() ] & 0x80) == 0
? result
: - result ;
}
} ;

template< int byteOrder >
struct ShortConverter
{
int i0() const { return byteOrder / 10 % 10 - 1
; }
int i1() const { return byteOrder % 10 - 1 ; }

short operator()( unsigned char bytes[ 2 ] )
const
{
return bytes[ i0() ] | (bytes[ i1() ] << 8) ;
}
} ;

avec des typedef's :

typedef ShortConverter< 12 >
IntelShortCvt ;
typedef FloatConverter< 1234, 126 >
IntelFloatCvt ;
typedef ShortConverter< 12 >
DECShortCvt ;
typedef FloatConverter< 3412, 128 >
DECFloatCvt ;
typedef ShortConverter< 21 >
MIPSShortCvt ;
typedef FloatConverter< 4321, 126 >
MIPSFloatCvt ;

Cette fois-ci, j'ai testé mon code pour le format Intel -- en
lisant sur un Sparc, qui n'a pas le même ordre d'octets. Pour
DEC, je me base sur le code que tu as posté, et pour MIPS, je
dévine un peu -- mais vu l'époque où est sorti le processeur,
IEEE me semble probable, et j'ai lu quelque part que c'était
grand-boutien.

Heureusement qu'il n'y a pas de double, ni un format IBM:-).

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Michael
Dans tous les cas ?


J'ai deux fichiers de test, un écrit avec des flottants DEC et l'autre
avec des flottants Intel. Les deux fonctionnent.

Je sais que le format des short est identique entre Intel et
DEC. Je crois aussi que le format des float (mais non des
doubles) est très semblable aussi. En revanche, je ne crois pas
que le format DEC supporte les dénormalisés, et je ne sais pas
en ce qui concerne les Inf et les NaN. Mais il se peut que ce ne
soit pas important -- que de telles valeurs ne t'intéressent
pas. (Aussi, si DEC ne supporte pas les dénormalisés, ça veut
dire simplement qu'ils n'apparaîtront pas.)


Que veut dire dénormalisé? Inf? Nan?

En ce qui concerne le troisième format : il y a tout à parier
qu'il suffit d'inverser les octets par rapport au format Intel.


Comment on inverse les octets?

En somme, c'est à peu près IEEE sur un long avec l'ordre des
octets 3412. Et l'exposant est excès 128, plutôt que excès 126.
On pourrait prèsqu'en faire une classe templatée.


A quoi correspond l'excès?

float IntelDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

float * f = (float *)tmp;
float flt = *f;


Attention ici. Ce que tu as fait dépend beaucoup de la machine.
(J'en conclus, d'ailleurs, que tu travaille sur un Intel. Ça ne
marcherait pas sur un Sparc.)


Oui, je suis sur PC... J'imagine qu'AMD == Intel, c'est l'architecture
x86 qui détermine je suppose?

Essaie d'utiliser la même stratégie pour les flottants Intel que
pour les flottants DEC, puis inverser l'ordre des octets, et tu
dois pouvoir traiter le troisième format aussi.

Au fond, je crois qu'effectivement, on s'en tire très bien avec
des templates :

template< int byteOrder, int exponentExcess >
struct FloatConverter
{
int i0() const { return byteOrder / 1000 % 10
-
1 ; }
int i1() const { return byteOrder / 100 % 10 -
1 ; }
int i2() const { return byteOrder / 10 % 10 -
1
; }
int i3() const { return byteOrder % 10 - 1 ; }

float operator()( unsigned char bytes[ 4 ] )
const
{
float result = 0.0 ;
int exp = ((bytes[ i3() ] & 0x7F) << 1)
| (bytes[ i2() ] >> 7) ;
uint_least32_t mantisse = bytes[ i0() ]
| (bytes[ i1() ] << 8)
| ((bytes[ i2() ] & 0x7F) << 16)
;
if ( exp == 0 && mantisse != 0 ) {
// dénormalisé...
} else if ( exp == 0xFF ) {
// NaN, Inf...
} else {
result = ldexp( mantisse | 0x800000, exp -
exponentExcess - 24) ;
}
return (bytes[ i3() ] & 0x80) == 0
? result
: - result ;
}
} ;

template< int byteOrder >
struct ShortConverter
{
int i0() const { return byteOrder / 10 % 10 -
1
; }
int i1() const { return byteOrder % 10 - 1 ; }

short operator()( unsigned char bytes[ 2 ] )
const
{
return bytes[ i0() ] | (bytes[ i1() ] << 8) ;
}
} ;

avec des typedef's :

typedef ShortConverter< 12 >
IntelShortCvt ;
typedef FloatConverter< 1234, 126 >
IntelFloatCvt ;
typedef ShortConverter< 12 >
DECShortCvt ;
typedef FloatConverter< 3412, 128 >
DECFloatCvt ;
typedef ShortConverter< 21 >
MIPSShortCvt ;
typedef FloatConverter< 4321, 126 >
MIPSFloatCvt ;



Je testerai ça ce soir...

Cette fois-ci, j'ai testé mon code pour le format Intel -- en
lisant sur un Sparc, qui n'a pas le même ordre d'octets. Pour
DEC, je me base sur le code que tu as posté, et pour MIPS, je
dévine un peu -- mais vu l'époque où est sorti le processeur,
IEEE me semble probable, et j'ai lu quelque part que c'était
grand-boutien.


Ca veut dire quoi grand-boutien?

Heureusement qu'il n'y a pas de double, ni un format IBM:-).


Heureusement en effet ;)

Encore merci de toutes tes explications et de ton aide...


Avatar
kanze
Michael wrote:
Dans tous les cas ?


J'ai deux fichiers de test, un écrit avec des flottants DEC et
l'autre avec des flottants Intel. Les deux fonctionnent.


Je me posais la question sur les valeurs. Surtout des valeurs
particulières disponibles sur Intel. Des cas limites, donc.

Je sais que le format des short est identique entre Intel et
DEC. Je crois aussi que le format des float (mais non des
doubles) est très semblable aussi. En revanche, je ne crois
pas que le format DEC supporte les dénormalisés, et je ne
sais pas en ce qui concerne les Inf et les NaN. Mais il se
peut que ce ne soit pas important -- que de telles valeurs
ne t'intéressent pas. (Aussi, si DEC ne supporte pas les
dénormalisés, ça veut dire simplement qu'ils n'apparaîtront
pas.)


Que veut dire dénormalisé? Inf? Nan?


Dénormalisé, c'est un flottant dont l'exposant est 0, mais la
mantisse ne l'est pas. Il apparaît sur Intel (mais pas sur DEC,
si mes souvenirs sont exacte) lorsque le résultat d'un calcul
est trop petit pour être représenté avec la mantisse normalisée
(c-à-d avec le bit de poid fort 1) et une exposant non-nul.

Inf, c'est des représentations spéciales pour infini (+ et -) ;
il apparaît en cas de débordement. NaN signifie « Not A
Number », et apparaît comme le résultat des calculs pas bien
définis, du genre 0/0. (Sur un Intel, x/0 donne Inf, avec la
signe d'x, sauf si x est aussi 0.)

Ce sont des valeurs spéciales. À la rigueur, tu dis que tu ne
les supportes pas. Mais il vaut mieux savoir quand même qu'ils
existent, et si tu ne veux pas les traiter, il vaudrait mieux
quand même les reconnaître, et générer une erreur quand tu en
rencontres un.

Sur Intel (et en général, d'ailleurs), si l'exposant est 0, et
la mantisse ne l'est pas, c'est un dénormalisé. Sur Intel, et
probablement sur MIPS, si l'exposant brut est 0xFF (sa valeur
max), c'est infini (si la mantisse est 0) ou un NaN.

En ce qui concerne le troisième format : il y a tout à
parier qu'il suffit d'inverser les octets par rapport au
format Intel.


Comment on inverse les octets?


En les lisant ou les écrivant dans l'ordre inversé. À la place
de :
return tmp[ 1 ] << 8 | tmp[ 0 ] ;
on fait
return tmp[ 0 ] << 8 | tmp[ 1 ] ;
Par exemple.

En principe, il y a n! ordres possibles pour une entité d'n
bytes.

(En passant : si tu ne connais pas encore les opérateurs du
genre << et | en C++, il faut les apprendre pour ce
problème-ci.)

En somme, c'est à peu près IEEE sur un long avec l'ordre des
octets 3412. Et l'exposant est excès 128, plutôt que excès
126. On pourrait prèsqu'en faire une classe templatée.


A quoi correspond l'excès?


C'est une façon de représenter les valeurs négatives. À la place
des représentations habituelles (complément à deux, etc.), où 0
est toujours représenté par tous les bits à 0, on ajoute
systèmatiquement une valeur constante à la valeur à représenter.
C'est l'excès : en IEEE, la valeur 126 dans le champs d'exposant
signifie un exposant de 0, 125 un exposant de -1, etc.
Techniquement, ça se justifie quand on regarde comment se fait
les calculs avec les flottants.

float IntelDataHandler::readFloat(std::ifstream & source ) const
{
unsigned char tmp[ 4 ] ;
if (!source.read(reinterpret_cast<char*>(tmp),4))
throw Exception("Impossible de lire le fichier");

float * f = (float *)tmp;
float flt = *f;


Attention ici. Ce que tu as fait dépend beaucoup de la machine.
(J'en conclus, d'ailleurs, que tu travaille sur un Intel. Ça ne
marcherait pas sur un Sparc.)


Oui, je suis sur PC... J'imagine qu'AMD == Intel, c'est
l'architecture x86 qui détermine je suppose?


Tout à fait.

Les entiers sont à complément à deux, et les flottants utilisent
le format IEEE. Ce qui est aussi le cas sur beaucoup d'autres
machines, comme par exemple le Sparc sur laquelle je travaille.
Du coup, les seules différences, c'est dans le nombre et l'ordre
des octets : si on régarde les float comme des valeurs à 32
bits, ils sont exactement pareils sur Intel/AMD et sur Sparc,
mais si on les régardent comme un tableau de 4 octets, l'ordre
des octets est inversés.

Aujourd'hui, les architectures à une multiple de 8 bits, avec
les entiers négatifs en compléments à deux et les flottants en
format IEEE dominent -- autant que je sache, il n'y a plus que
l'Unisys 2200, et les gros IBM pour les flottants, qui s'en
écartent. Mais historiquement, ce n'était pas le cas. Surtout
pour les représentations flottantes. La représentation DEC ici
en est un exemple. (Encore que je me démande... Les VAX ont
répris le format des PDP-11, je crois ; est-ce que les Alpha ont
répris le format des VAX ?)

[...]
Cette fois-ci, j'ai testé mon code pour le format Intel --
en lisant sur un Sparc, qui n'a pas le même ordre d'octets.
Pour DEC, je me base sur le code que tu as posté, et pour
MIPS, je dévine un peu -- mais vu l'époque où est sorti le
processeur, IEEE me semble probable, et j'ai lu quelque part
que c'était grand-boutien.


Ca veut dire quoi grand-boutien?


« Big-endian », en anglais. Il réfère à l'ordre des octets dans
un mot. Si l'octet de poids fort se trouve à l'adresse faible,
la machine est grand-boutienne. Si c'est l'octet de poids
faible, la machine est petit-boutienne.

C'est un peu naïf, parce qu'il suppose qu'il n'y a que deux
ordres possibles. Ce qui est vrai pour les mots de deux octets,
mais j'ai déjà rencontré trois ordres différents pour les mots
de quatres octets -- en fait, les DEC (PDP-11, mais non les
VAX), les Intel et les MIPS ont trois ordres différents.

À titre d'exemple, considère :

int
main()
{
uint32_t i = 0x04030201 ;
uint8_t* p = reinterpret_cast< uint8_t* >( &i) ;
for ( int i = 0 ; i < 4 ; ++ i ) {
std::cout << (int)p[ i ] ;
}
std::cout << std::endl ;
return 0 ;
}

Sur un Intel, tu verras apparaître "1234", sur un Sparc "4321",
et sur un PDP-11 (si tu arrives à en trouver un) "3412".

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Jean-Marc Bourguet
"kanze" writes:

Aujourd'hui, les architectures à une multiple de 8 bits, avec
les entiers négatifs en compléments à deux et les flottants en
format IEEE dominent -- autant que je sache, il n'y a plus que
l'Unisys 2200, et les gros IBM pour les flottants, qui s'en
écartent. Mais historiquement, ce n'était pas le cas. Surtout
pour les représentations flottantes. La représentation DEC ici
en est un exemple. (Encore que je me démande... Les VAX ont
répris le format des PDP-11, je crois ; est-ce que les Alpha ont
répris le format des VAX ?)


Il y avait au moins 3 formats de flottants sur VAX (D, F, G,
H), si j'ai bonne mémoire 2 tailles (32/64 bits) et des
choix différents pour le compromis précision/intervalle.

Je crois que les alpha avaient du support pour les formats
IEEE et les formats VAX.

Sur un Intel, tu verras apparaître "1234", sur un Sparc
"4321", et sur un PDP-11 (si tu arrives à en trouver un)
"3412".


J'allais envoyer vers http://www.mentec-inc.com mais
apparemment, même chez eux c'est épuisé... Il y a toujours
des émulateurs (http://pdp11.org/).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

1 2 3