web-dev-qa-db-fra.com

Existe-t-il un moyen de vérifier si une chaîne peut être un float en C?

Vérifier si cela peut être un int est assez simple - il suffit de vérifier que chaque chiffre est compris entre '0' et '9'. Mais un flotteur est plus difficile. J'ai trouvé ceci , mais aucune des réponses ne fonctionne vraiment. Considérez cet extrait de code, basé sur la réponse la plus élevée (acceptée):

float f;
int ret = sscanf("5.23.fkdj", "%f", &f);
printf("%d", ret);

1 sera imprimé.


Une autre réponse a suggéré d'utiliser strpbrk, pour vérifier si certains caractères illégaux sont présents, mais cela ne fonctionnerait pas non plus, car 5fin7 ne serait pas légal, mais inf le serait.


Encore un autre réponse suggéré de vérifier la sortie de strtod. Mais considérez ceci:

char *number = "5.53 garbanzo beans"
char *foo;

strtod(number, &foo);

printf("%d", isspace(*foo) || *foo == '\0'));

Ça va imprimer 1. Mais je ne veux pas supprimer complètement l'appel isspace, car " 5.53 " devrait être un nombre valide.


Existe-t-il un moyen élégant, idiomatique de faire ce que j'essaie de faire?

16
Elronnd

La première réponse devrait fonctionner si vous le combinez avec %n, qui correspond au nombre de caractères lus:

int len;
float ignore;
char *str = "5.23.fkdj";
int ret = sscanf(str, "%f %n", &ignore, &len);
printf("%d", ret==1 && !str[len]);

!str[len] expression sera false si la chaîne contient des caractères non inclus dans float. Notez également un espace après %f pour résoudre les espaces de fin.

Démo

13
dasblinkenlight

Vous pouvez vérifier si - après avoir lu une valeur avec strtod - le reste consiste uniquement en espaces blancs. La fonction strspn peut vous aider ici, et vous pouvez même définir "votre ensemble personnel d'espaces blancs" à prendre en compte:

int main() {

    char *number = "5.53 garbanzo beans";
    char *foo;

    double d = strtod(number, &foo);
    if (foo == number) {
        printf("invalid number.");

    }
    else if (foo[strspn(foo, " \t\r\n")] != '\0') {
        printf("invalid (non-white-space) trailing characters.");
    }
    else {
        printf("valid number: %lf", d);
    }
}
9
Stephan Lechner

Est-il possible de vérifier si une chaîne peut être un float?

Un problème lié à l'approche sscanf(...,"%f") est un dépassement de capacité, qui correspond à UB. Pourtant, il est généralement bien géré.

Utilisez plutôt float strtof(const char * restrict nptr, char ** restrict endptr);

int float_test(const char *s) {
  char *ednptr;
  errno = 0;
  float f = strtof(s, &endptr);
  if (s == endptr)  {
    return No_Conversion;
  }
  while (isspace((unsigned char) *endptr)) {  // look past the number for junk
    endptr++;
  }   
  if (*endptr) {
    return Extra_Junk_At_End; 
  }

  // If desired
  // Special cases with with underflow not considered here.
  if (errno) {
    return errno; // likely under/overflow
  }  

  return Success;
}
5
chux

Il s'agit d'une variante du fragment de code publiée par dasblinkenlight qui est légèrement plus simple et plus efficace, car strlen(str) pourrait être coûteux:

const char *str = "5.23.fkdj";
float ignore;
char c;
int ret = sscanf(str, "%f %c", &ignore, &c);
printf("%d", ret == 1);

Explication: sscanf() renvoie 1 si et seulement si un float a été converti, suivi d'un espace facultatif et de aucun autre caractère.

3
chqrlie

Ce code est étroitement basé sur le answer by dasblinkenlight . Je le propose comme matière à réflexion. Certaines des réponses fournies peuvent ne pas être ce que vous vouliez.

#include <stdio.h>
#include <string.h>

static void test_float(const char *str)
{
    int len;
    float dummy = 0.0;
    if (sscanf(str, "%f %n", &dummy, &len) == 1 && len == (int)strlen(str))
        printf("[%s] is valid (%.7g)\n", str, dummy);
    else
        printf("[%s] is not valid (%.7g)\n", str, dummy);
}

int main(void)
{
    test_float("5.23.fkdj");        // Invalid
    test_float("   255.   ");       // Valid
    test_float("255.123456");       // Valid
    test_float("255.12E456");       // Valid
    test_float("   .255   ");       // Valid
    test_float("   Inf    ");       // Valid
    test_float(" Infinity ");       // Valid
    test_float("   Nan    ");       // Valid
    test_float("   255   ");        // Valid
    test_float(" 0x1.23P-24 ");     // Valid
    test_float(" 0x1.23 ");         // Valid
    test_float(" 0x123 ");          // Valid
    test_float("abc");              // Invalid
    test_float("");                 // Invalid
    test_float("   ");              // Invalid
    return 0;
}

En testant sur un Mac sous macOS Sierra 10.12.6 en utilisant GCC 7.1.0 comme compilateur, je reçois le résultat:

[5.23.fkdj] is not valid (5.23)
[   255.   ] is valid (255)
[255.123456] is valid (255.1235)
[255.12E456] is valid (inf)
[   .255   ] is valid (0.255)
[   Inf    ] is valid (inf)
[ Infinity ] is valid (inf)
[   Nan    ] is valid (nan)
[   255   ] is valid (255)
[ 0x1.23P-24 ] is valid (6.775372e-08)
[ 0x1.23 ] is valid (1.136719)
[ 0x123 ] is valid (291)
[abc] is not valid (0)
[] is not valid (0)
[   ] is not valid (0)

Les nombres hexadécimaux risquent d'être particulièrement problématiques. Les différentes formes d’infini et de nombres non pervers pourraient également poser problème. Et le seul exemple avec un exposant (255.12E456) déborde float et génère une infinité - est-ce vraiment OK?

La plupart des problèmes soulevés ici sont de définition - comment définissez-vous ce que vous voulez être acceptable. Mais notez que strtod() accepterait toutes les chaînes valides (et quelques-unes des chaînes non valides, mais d’autres tests révéleront ces problèmes).

Il est clair que le code de test peut être révisé pour utiliser un tableau d'une structure contenant une chaîne et le résultat souhaité, ce qui peut être utilisé pour parcourir les scénarios de test affichés et les extras que vous ajoutez.

La conversion du résultat de strlen() évite un avertissement de compilation (erreur car je compile avec -Werror) - comparison between signed and unsigned integer expressions [-Werror=sign-compare]. Si vos chaînes sont suffisamment longues pour que le résultat de strlen() déborde d'une signature int signée, vous rencontrez d'autres problèmes pour faire croire qu'elles sont des valeurs valides. OTOH, vous voudrez peut-être expérimenter avec 500 chiffres après un signe décimal - c'est valide.

Ce code note les commentaires faits à la réponse de dasblinkenlight:

3
Jonathan Leffler

Peut être ça? Pas très bon mais peut faire le travail. Renvoie -1 en cas d'erreur 0 si aucune conversion n'est effectuée et> 0 avec les indicateurs convertis définis.

#define INT_CONVERTED       (1 << 0)
#define FLOAT_CONVERTED     (1 << 1)
int ReadNumber(const char *str, double *db, int *in)
{

    int result = (str == NULL || db == NULL || in == NULL) * -1;
    int len = 0;
    char *tmp;

    if (result != -1)
    {
        tmp = (char *)malloc(strlen(str) + 1);
        strcpy(tmp, str);
        for (int i = strlen(str) - 1; i >= 0; i--)
        {
            if (isspace(tmp[i]))
            {
                tmp[i] = 0;
                continue;
            }
            break;
        }
        if (strlen(tmp))
        {
            if (sscanf(tmp, "%lf%n", db, &len) == 1 && strlen(tmp) == len)
            {
                result |= FLOAT_CONVERTED;
            }
            if (sscanf(tmp, "%d%n", in, &len) == 1 && strlen(tmp) == len)
            {
                result |= INT_CONVERTED;
            }
        }
        free(tmp);
    }
    return result;
}
0
P__J__