Partage
  • Partager sur Facebook
  • Partager sur Twitter

type non interprété ou cast "binaire"

Sujet résolu
    16 juin 2019 à 2:37:29

    Bonjour,

    Je n'en avais pas eu besoin jusque là, mais me voilà contraint de stocker des flottants dans un fichier.

    J'ai voulu utiliser la méthode standard habituelle, mais j'ai rencontré un problème lors de l'écriture / lecture et j'ai été obligé de ruser :

    int fileTest(void)
    {
    
        readWriteErr = 0;
        FILE *file = fopen("fileTest.txt", "wb");
    
        if(IS_NULL(file))
        {
            printf("err open\n");
            return FATAL;
        }
    
        float value = 54.236;
        int32_t a = value, //54 parce qu'interprété
                b = *(int32_t*)&value; //1113125290 parce que non interprété
    
    
        printf("value is %f and sizes %d, 'a' is %d, 'b' is %d\n", value, sizeof(value), a, b);
    
        writeFloat_32le(&value, file);
    
        if(readWriteErr)
        {
            printf("err W\n");
            fclose(file);
            return FATAL;
        }
    
        fclose(file);
        file = NULL;
    
        file = fopen("fileTest.txt", "rb");
        if(IS_NULL(file))
        {
            printf("err open\n");
            return FATAL;
        }
        rewind(file);
    
        value = readFloat_32le(file);
    
        if(readWriteErr)
        {
            printf("err R\n");
            fclose(file);
            return FATAL;
        }
    
        printf("value is %f\n", value);
        
        fclose(file);
    }
    
    void writeFloat_32le(float *val, FILE *f)
    {
        uint32_t *temp32 = (uint32_t*)val;
        uint8_t b[4];
    
        b[0] = (*temp32)     & 0xff;
        b[1] = (*temp32>>8)  & 0xff;
        b[2] = (*temp32>>16) & 0xff;
        b[3] = (*temp32>>24) & 0xff;
    
        if(fwrite(b, 1, sizeof(b), f) != sizeof(b)) //ecrit LE
            readWriteErr++;
    }
    
    float readFloat_32le(FILE* f)
    {/*lecture dans fichier le*/
        uint8_t b[4];
        uint32_t temp32 = 0;
    
        if(fread(b, 1, sizeof(b), f) != sizeof(b))
            readWriteErr++;
    
         temp32 = b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24;//lit LE
    
         return *((float*)&temp32);
    }
    Dans un premier temps pour l'écriture dans le fichier, le compilo refuse de considérer un float comme une bête suite d'octets et refuse les opérateurs bitwise dessus, d'où une petite triche en castant en pointeur sur entier (writeFloat_32LE)

    Ensuite lors de la lecture, même topo, je relis mes octets et les remets dans l'ordre, mais la fonction retourne une interprétation de la valeur entière. Du coup, encore obligé de jouer le sioux.

    Bref, je cherche un truc simple pour dire "copier le code binaire sans réfléchir", comme pour la variable b dans floatfiletest(), mais en évitant les casts douteux et lourdingues.

    Parce que 54.236 est codé 01000010 01011000 11110001 10101010 et que quand je fais a=value je me retrouve avec a=54 qui est codé 110110, autant dire que ça n'a pas grand chose à voir... Même si c'est plutôt pratique dans les 99,99999... autres pourcents des cas.

    Ou même un type non-interprété, comme les byte, word, dword qu'on a sur d'autres systèmes et qui sont juste des suites d'octets : ni entiers ni flottants, ni nombres ni caractères. Alors, oui, ils sont dans windef.h mais ça ne résout pas le problème, ça reste des defines de valeurs typés de base.

    D'ailleurs, une question supplémentaire me vient du coup : pourquoi ne pas avoir autorisé le bitwise sur les flottants ?

    Merci.

    -
    Edité par drx 16 juin 2019 à 2:41:08

    • Partager sur Facebook
    • Partager sur Twitter

    Bonhomme !! | Jeu de plateforme : Prototype.

      16 juin 2019 à 10:09:54

      Une solution de cast est de passer par un union

      #include <stdio.h>
      #include <stdint.h>
      
      typedef union FloatConverter FloatConverter;
      union FloatConverter
      {
       float toFloat;
       uint32_t toUint32;
       unsigned char toByte[sizeof(float)];
      };
      
      
      
      int main(void) {
      FloatConverter floatc;
      floatc.toFloat= 54.236;
      
      printf("float =%f\n",floatc.toFloat);
      printf("uint32 =%u\n",floatc);
      
      printf("bytes =");
      for(int i=0;i<sizeof(floatc);i++)
       printf("[%x]",floatc.toByte[i]);
      puts("");
      	return 0;
      }
      
      
      //résultat
      /*
      float =54.236000
      uint32 =1113125290
      bytes =[aa][f1][58][42]
      */



      • Partager sur Facebook
      • Partager sur Twitter
        16 juin 2019 à 13:58:03

        Salut,

        Effectivement, c'est une autre possibilité. Mais ça reste plutôt indirect et plus lourd qu'une astuce de cast. Il y avait aussi memcpy au pire.

        De plus, je ne suis pas certain que ma méthode soit très standard. J'ai supposé un moment que je devrais écrire et relire séparément signe, exposant et mantisse sous forme d'entiers, mais si je tombe sur un float qui ne soit pas en IEEE754, je vais avoir le même problème sur la déconstruction / reconstruction. A moins de faire du cas par cas. Peut-être qu'à partir de FLT_RADIX et FLT_MANT_DIG on pourrait reconstruire un float de façon standard... Je vais de ce pas tester ça.

        Salutations.

        • Partager sur Facebook
        • Partager sur Twitter

        Bonhomme !! | Jeu de plateforme : Prototype.

          16 juin 2019 à 20:33:49

          Après si c'est juste stocker un flottant dans un fichier..

          fwrite(&monFloat, 1, sizeof(float), f);

          Mais je ne comprend pas ce que tu souhaite faire exactement je pense.

          • Partager sur Facebook
          • Partager sur Twitter
            16 juin 2019 à 21:17:30

            Re,

            Je cherche à écrire et relire une valeur flottante en m'assurant de toujours la retrouver lorsque le fichier source change de système.

            par exemple un int peut faire 32 ou 64 selon, donc si je fait un couple

            fwrite(&monInt, 1, sizeof(int), f); et fread(&monInt, 1, sizeof(int), f); je peux écrire sur 32 d'un côté et relire sur 64 de l'autre, et là... C'est le drame. Sans parler de l'endianness.

            Du coup, je force écriture et lecture sur N octets ordonnés (le ou be) pour être certain de m'y retrouver :

            uint32_t read_u32le(FILE* f)
            {/*lecture u32 bits dans fichier le*/
                uint8_t b[4];
            
                if(fread(b, 1, sizeof(b), f) != sizeof(b))
                    readWriteErr++;
            
                return 0U | b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24;
            }
            
            void write_u32le(uint32_t val, FILE*f)
            {/*ecriture en le dans fichier*/
                uint8_t b[4];
            
            
                b[0] = val       & 0xff;
                b[1] = (val>>8)  & 0xff;
                b[2] = (val>>16) & 0xff;
                b[3] = (val>>24) & 0xff;
            
                if(fwrite(b, 1, sizeof(b), f) != sizeof(b))
                    readWriteErr++;
            }

            Je cherche à retrouver cette logique pour un flottant.

            • Partager sur Facebook
            • Partager sur Twitter

            Bonhomme !! | Jeu de plateforme : Prototype.

              17 juin 2019 à 0:08:28

              drx a écrit:

              Je cherche à écrire et relire une valeur flottante en m'assurant de toujours la retrouver lorsque le fichier source change de système.

              La méthode du fwrite fonctionne très bien dans ce cas.

              Ne voudrais-tu pas plutôt pouvoir écrire un flottant avec un 1er système, puis le relire avec un 2e système? Pour cela, la bonne nouvelle est que le format des float et double est quasi-standard, si bien qu'il est documenté dans l'annexe F de la norme du langage C. Donc le seul problème qu'il reste devrait être l'endianess https://en.wikipedia.org/wiki/Endianness#Floating_point

              Et pour éviter les questions d'endianess, il reste la sérialisation en texte. Il me semble que le format '%a' garantit une précision suffisante.

              drx a écrit:

              D'ailleurs, une question supplémentaire me vient du coup : pourquoi ne pas avoir autorisé le bitwise sur les flottants ?

              Sans doute car le résultat aurait été impossible à spécifier. Et si l'on veut considérer un flottant comme un tableau de bytes, mais du coup ce n'est plus un flottant, juste une suite de bits, et bien il y a la méthode que tu as présentée dans ton premier message. La méthode que tu as présentée, avec les conversions de pointeur, est me semble-t-il une méthode classique.


              • Partager sur Facebook
              • Partager sur Twitter
                17 juin 2019 à 0:25:11

                Bonsoir,

                Marc Mongenet a écrit:

                drx a écrit:

                Je cherche à écrire et relire une valeur flottante en m'assurant de toujours la retrouver lorsque le fichier source change de système.

                La méthode du fwrite fonctionne très bien dans ce cas.

                Ne voudrais-tu pas plutôt pouvoir écrire un flottant avec un 1er système, puis le relire avec un 2e système?[...]

                Si, c'est ce que je voulais dire par "la retrouver quand le fichier source (sous-entendu à lire) change de système." Par exemple pour garantir la portabilité des fichiers de sauvegardes en changeant d'OS. Je n'ai d'autre choix que de recompiler le programme sur chaque plateforme, mais les fichiers de ressources et de sauvegardes doivent être lus correctement indépendamment de ça.

                Mais si le jeu des conversions de pointeurs ne choque personne, alors ça me va tant que c'est portable.

                Merci.

                • Partager sur Facebook
                • Partager sur Twitter

                Bonhomme !! | Jeu de plateforme : Prototype.

                  17 juin 2019 à 2:38:56

                  drx a écrit:

                  Mais si le jeu des conversions de pointeurs ne choque personne, alors ça me va tant que c'est portable.

                  Il me semble que c'est ni plus ni moins portable qu'un simple fwrite pour ce qui concerne float et double. Une complication inutile donc.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    17 juin 2019 à 18:14:43

                    drx a écrit:

                    Mais si le jeu des conversions de pointeurs ne choque personne, alors ça me va tant que c'est portable.

                    La conversion de pointeurs n'est malheureusement pas du tout portable. As-tu essayé ton code en mode release optimisé?
                    Absolument rien ne dit que le code uint32_t *temp32 = (uint32_t*)val; fonctionne. L'optimiseur a le droit de considérer que 2 données de type différents ne peuvent pas être à la même adresse (voir le strict aliasing en C). En activant les warnings maximums, ton compilateur devrait directement te le signaler (par exemple avec l'option -fstrict-aliasing pour gcc)

                    En utilisant l'union, il y a aussi problème d'aliasing, mais je n'ai jamais vu de compilateur qui ne gérait pas cela correctement (contrairement au cast qui produit n'importe quoi dès que le code est optimisé.)

                    union FloatConverter
                    {
                     float toFloat;
                     uint32_t toUint32;
                     unsigned char toByte[sizeof(float)];
                    };
                    • Partager sur Facebook
                    • Partager sur Twitter

                    En recherche d'emploi.

                    type non interprété ou cast "binaire"

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown