Forum Linux.debian/ubuntu [Résolu] Problème de séparateur décimal

Posté par . Licence CC by-sa
Tags :
1
15
oct.
2013

Salut à tous,

J'ai une machine virtuelle et un serveur sur lesquels j'ai installé des Ubuntu 12.04 (LTS donc), qui sont configurées plus ou moins de la même façon (package, environnement…).
Je tente d'y faire tourner un programme développé en C++ et compilé par mes soins et qui utilise boost::program_options pour gérer les options de la ligne de commande : ./pouet -l 42.5
Sur la machine virtuelle, tout fonctionne comme prévu.
Sur le serveur, il faut que je change le séparateur décimal pour une virgule : ./pouet -l 42,5 . Ou bien que je force export LC_NUMERIC=C.

Pourtant, sur les deux installations, les locales semblent bonnes :

$ locale
LANG=fr_FR.UTF-8
LANGUAGE=fr:en
LC_CTYPE=fr_FR.UTF-8
LC_NUMERIC="fr_FR.UTF-8"
LC_TIME="fr_FR.UTF-8"
LC_COLLATE=fr_FR.UTF-8
LC_MONETARY="fr_FR.UTF-8"
LC_MESSAGES=fr_FR.UTF-8
LC_PAPER="fr_FR.UTF-8"
LC_NAME="fr_FR.UTF-8"
LC_ADDRESS="fr_FR.UTF-8"
LC_TELEPHONE="fr_FR.UTF-8"
LC_MEASUREMENT="fr_FR.UTF-8"
LC_IDENTIFICATION="fr_FR.UTF-8"
LC_ALL=

Et si je compile et exécute ces quelques lignes ce C, tout va bien :

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
        double a = atof("42.5");
        printf("%lf\n", a);

        return 0;
}

Si l'un d'entre vous à une idée, je suis preneur.

  • # info

    Posté par (page perso) . Évalué à 2.

    Les informations que tu nous donnes concernent le serveur ou ta machine virtuelle ? Ou est-ce une configuration identique ?

    Quoiqu'il en soit, je dirai, au vu des informations d'internationalisation fournies, que le comportement au niveau du serveur est le bon. Pour la locale fr_FR, le séparateur décimale est bien la virgule et non le point.

    Ensuite, la fonction atof considère l'entrée comme utilisant une locale bien définie (du en_US me semble-t-il) indépendante de la configuration de l'environnement.

    • [^] # Re: info

      Posté par . Évalué à 0.

      En effet, il manquait l'info comme quoi la commande locale retourne la même chose sur les 2 machines.
      Ainsi que celle disant que si, sur le serveur, je force LC_NUMERIC=C, je peux utiliser le point comme séparateur décimal.

      La configuration des deux machines est sensiblement identique.

      Oui, en français le séparateur décimal est la virgule. Mais mis à part les tableurs qui sont relativement sensibles sur ce point, je n'ai jamais tapé de nombre décimaux avec une virgule dans mon terminal.

      De ce que je me souviens, atof() utilise par défaut la locale C, qui justement spécifie le point comme séparateur décimal.

      Du coup, c'est plutôt le comportement de ma machine virtuelle qui me parait être le bon.

      • [^] # Re: info

        Posté par (page perso) . Évalué à 1.

        Quand je disais que le comportement sur le serveur semblait être bon, c'est que la locale était française et qu'il accepte les nombres dont le séparateur décimal est la virgule ;)

        La question ici qui se pose pour pouvoir répondre à ton problème, c'est de savoir où est parsée l'entier sous forme de chaine de caractère en flottant. Est-ce que tu utilises la fonction atof pour cela ? Est-ce Boost qui s'en occupe ?

        Si tu utilises atof, le serveur a clairement un problème ! Si tu utilises Boost, il faut voir ce qui peut influencer le parsing.

        • [^] # Re: info

          Posté par . Évalué à 0. Dernière modification le 15/10/13 à 19:34.

          C'est moi qui parse la valeur, à coup de strtof() (parce que les strtoX() sont les seules méthodes qui permettent de vérifier que ce que l'on a obtenu est correct (sans avoir à sortir l'artillerie lourde, genre boost::lexical_cast)).

          Du coup, j'ai tenté de vérifier ma locale juste avant l'appel à strtof().

          D'après http://www.cplusplus.com/reference/locale/locale/locale/, en C++ :
          std::locale g = std::locale(); est sensé me donner ma locale globale (pas certain de ce que cela signifie),
          std::locale d = std::locale(""); est sensé me donner la locale par défaut du système,
          std::use_facet< std::numpunct >(la_locale).decimal_point(); est sensé me donner le séparateur décimal.

          Sur les deux machines, la locale par défaut est C avec comme séparateur décimal est le point, et la locale globale est bien fr_FR.UTF-8 avec la virgule. Mais je ne suis pas certain de savoir laquelle est utilisée…

          Sauf qu'à priori, comme strtof() est une fonction C et non C++, elle utilise une autre locale.
          D'après http://www.cplusplus.com/reference/clocale/localeconv/, les lignes suivantes me donne le séparateur décimal :

          struct lconv * lc = localeconv();
          std::cout << lc->decimal_point << std::endl;

          Sur ma machine virtuelle, c'est bien le point. Sur le serveur, c'est la virgule. Ce qui explique le comportement du serveur. Reste à trouver pourquoi il utilise une locale différente. Moi qui avait pris une LTS en pensant être tranquille…

          Je pense qu'en attendant, je vais changer pour du lexical_cast.

          • [^] # Re: info

            Posté par (page perso) . Évalué à 2.

            Si tu utilises strtof, alors la version qui s'exécute sur ton serveur est correct. Cette fonction tient compte de la locale, et plus particulièrement de LC_NUMERIC. Comme elle est initialisé avec une locale fr_FR, le séparateur est bien la virgule.

            Du coup, es-tu à 100% que sur ta machine virtuelle, LC_NUMERIC est à fr_FR ? Si oui, est-ce que cette locale est installée ? Car si non, cela pourrait expliquer le comportement que tu rencontres… ;)

            • [^] # Re: info

              Posté par . Évalué à 0.

              Non, la locale par défaut qui est sensée être utilisée par un programme en C est la locale "C".
              Source : http://www.cplusplus.com/reference/clocale/

              The "C" locale is the minimal locale. It is a rather neutral locale which has the same settings across all systems and compilers, and therefore the exact results of a program using this locale are predictable. This is the locale used by default on all C programs.

              J'ai testé sur deux autres machines (Debian stable et testing), toutes les deux configurées avec du fr_FR.UTF-8 comme locale, et le comportement celui que j'ai toujours vu : c'est le point qui est utilisé comme séparateur décimal.

              #include <stdio.h>
              #include <stdlib.h>
              #include <locale.h>
              
              int main(int argc, char** argv)
              {
                      float v;
                      char *l;
              
                      if(argc == 1) {
                              printf("Usage: %s 3.14\n", argv[0]);
                              return -1;
                      }
              
                      /* Get the name of the current locale.  */
                      l = setlocale (LC_NUMERIC, NULL);
                      printf("Locale used: %s\n", l);
              
                      v = strtof(argv[1], 0);
                      printf("The first argument is: %f\n", v);
              
                      return 0;
              }
              • [^] # Re: info

                Posté par (page perso) . Évalué à 1.

                Non, la locale par défaut qui est sensée être utilisée par un programme en C est la locale "C".

                Sur ce point, tu as juste. Au démarrage d'un programme, la locale est C.

                Après, je pense qu'il y a une mésentente par la suite. LC_NUMERIC représente à la fois la locale à utiliser pour la manipulation des nombres au sein d'un programme et une variable d'environnement.

                Tu disais initialement que tu étais obligé de faire un export LC_NUMERIC="C" pour que tu aies le comportement attendu. A cause de ça, j'avais supposé que tu avais un setlocale(LC_NUMERIC, "") qui initialisait la locale LC_NUMERIC utilisée par le programme courant en utilisant la valeur contenue dans la variable d'environnement LC_NUMERIC, et donc que les valeurs utilisées par le programme et la variable d'environnement étaient les mêmes. Et c'est finalement ce qui se passait ! Mais c'était involontaire :) (Saloperie de QT hein ;))

        • [^] # Re: info

          Posté par . Évalué à 0.

          Bon, en fait c'est juste dix fois plus simple d'utiliser boost::lexical_cast que des strtoX().

  • # Problème résolu

    Posté par . Évalué à 1.

    Et le coupable est : Qt.

    Il me manquait un commit sur la version que je compilais sur ma machine virtuelle.

    Commit qui transformait mon application console en application console Qt.

    Et Qt charge la locale du système, donc fr_FR.UTF-8.
    http://harmattan-dev.nokia.com/docs/library/html/qt4/qcoreapplication.html#locale-settings

    Merci à toi Francesco d'avoir pris un peu de son temps pour essayer de m'aider.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.