Gibt es eine Möglichkeit zu überprüfen, ob eine Zeichenfolge in C ein Float sein kann?

Lesezeit: 8 Minuten

Benutzer-Avatar
Mondkind

Zu prüfen, ob es ein Int sein kann, ist einfach genug – prüfen Sie einfach, ob jede Ziffer dazwischen liegt '0' und '9'. Aber ein Schwimmer ist schwieriger. Ich habe das gefunden, aber keine der Antworten funktioniert wirklich. Betrachten Sie dieses Code-Snippet basierend auf der besten (akzeptierten) Antwort:

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

1 wird gedruckt.


Eine andere Antwort schlug vor, zu verwenden strpbrkum zu überprüfen, ob bestimmte illegale Zeichen vorhanden sind, aber das würde auch nicht funktionieren, weil 5fin7 wäre nicht legal, aber inf möchten.


Eine weitere Antwort schlug vor, die Ausgabe von strtod zu überprüfen. Aber bedenke Folgendes:

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

strtod(number, &foo);

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

Es wird gedruckt 1. Aber ich möchte die nicht entfernen isspace ganz anrufen, weil " 5.53 " sollte eine gültige Zahl sein.


Gibt es einen guten, eleganten, idiomatischen Weg, um das zu tun, was ich versuche?

  • Was ist mit einem regulären Ausdruck?

    – SBS

    7. August 2017 um 19:52 Uhr

  • @Elronnd In diesem Fall möchten Sie wahrscheinlich zuerst die Sequenz tokenisieren, was bedeutet, dass dort Gewohnheit führende oder nachgestellte Leerzeichen sein und es wird sicherlich nicht so etwas geben 5.0 meters die mit einer Zahl beginnt, gefolgt von einem Leerzeichen, und dann andere Dinge enthält

    – DanielH

    7. August 2017 um 19:59 Uhr

  • Ihr “einfacher” Test für int enthält keine führenden Werte -

    – Stark

    7. August 2017 um 20:04 Uhr

  • Als allgemeine Regel habe ich herausgefunden, dass man, wenn man versucht, eine Grammatik zu schreiben, eine Grammatik schreibt. Ich war zuverlässig enttäuscht von jedem Versuch, den ich unternommen habe, um das Schreiben der Grammatik mit cleverem C-Code zu umgehen. Es gibt einen Grund, warum wir formale Repräsentationen von Grammatiken und die Software zu ihrer Analyse (LEX/YACC, BISON, ANTLR, Boost.Spirit usw.) erfunden haben.

    – Cort Ammon

    7. August 2017 um 23:39 Uhr

  • Es ist mir unklar, ob Sie nach einem “Float im Allgemeinen” suchen oder “in der C-Sprache als Float-Konstante zählen”. Letzteres könnte hexadezimale Gleitkommazahlen usw. enthalten, ist aber im ersteren Fall möglicherweise unerwünscht.

    – Rohr

    8. August 2017 um 6:45 Uhr

Benutzer-Avatar
Sergej Kalinitschenko

Die erste Antwort sollte funktionieren, wenn Sie sie mit kombinieren %ndas ist die Anzahl der gelesenen Zeichen:

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] Der Ausdruck ist falsch, wenn die Zeichenfolge Zeichen enthält, die nicht in enthalten sind float. Beachten Sie auch das Leerzeichen danach %f um nachgestellte Leerzeichen zu adressieren.

Demo

  • Dieser scheitert an ein paar meiner Testfälle. Es steht dass 5.5 und “5,5”. sind nicht Floats (obwohl sie es sind!) Und es sagt, dass ` 235.` ein Float ist (obwohl es keiner ist!)

    – Mondkind

    7. August 2017 um 20:05 Uhr

  • @Elronnd Ich habe Leerzeichen hinzugefügt %f um dieses Problem zu beheben (Demo).

    – Sergej Kalinitschenko

    7. August 2017 um 20:11 Uhr


  • Das führende Leerzeichen ist harmlos und symmetrisch, aber unnötig; %f (wie alle numerischen Konvertierungsspezifikationen) überspringt ohnehin führende Leerzeichen, ob Sie das wollen oder nicht.

    – Jonathan Leffler

    7. August 2017 um 20:17 Uhr

  • @JonathanLeffler Wow, das ist ein schöner Fang! Vielen Dank! Ich denke ich kann mir den Stern per Einstellung sparen len zu strlen(str)+1, aber das ist grob unlesbar und nicht intuitiv, und es würde mich zwingen, eine leere Zeichenfolge in Sonderfällen zu verwenden, also würde ich Ihrem Vorschlag folgen und eine ignorierte Variable verwenden. Vielen Dank!

    – Sergej Kalinitschenko

    7. August 2017 um 20:35 Uhr

  • In der Tat ret kann sein EOF wenn str ist leer. Hier ist eine Lösung: printf("%d", ret == 1 && str[len] == '\0');

    – chqrlie

    7. August 2017 um 23:07 Uhr

Sie können überprüfen, ob – nachdem Sie einen Wert gelesen haben, mit strtod – der Rest besteht ausschließlich aus Leerzeichen. Funktion strspn kann hier helfen, und Sie können sogar “Ihren persönlichen Satz von Leerzeichen” definieren, um dies zu berücksichtigen:

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);
    }
}

  • Dieser funktioniert für alle meine Testfälle bis auf einen. Es denkt, dass ` 235.` eine Zahl ist, aber das ist es nicht.

    – Mondkind

    7. August 2017 um 20:09 Uhr

  • Ich wurde gerade darüber informiert, dass ein Trailing . ist akzeptabel.

    – Mondkind

    7. August 2017 um 20:10 Uhr

  • Warum denkst du nicht nach " 255." als Schwimmer. Das strtod() Funktion würde es akzeptieren; es schreibt keine Ziffer nach dem Dezimalkomma vor. Es ist zulässig vorzuschreiben, dass es eine Ziffer vor und eine Ziffer nach dem Dezimalkomma geben muss, wenn das Dezimalkomma vorhanden ist, aber das erfordert zusätzliche Tests, da beides der Fall ist 255. und .255 sind legitime Gleitkommazahlen in C, und die strtod() Die Funktion akzeptiert sie. Wenn Sie dies jedoch vorschreiben (die Ziffer vor und nach der Regel), müssen Sie angeben, ob.

    – Jonathan Leffler

    7. August 2017 um 20:10 Uhr


  • @Elronnd 235 ist laut C-Compiler ein gültiger Float.

    – Sergej Kalinitschenko

    7. August 2017 um 20:10 Uhr

  • Hm – es kommt darauf an: double valid = 553.; gilt in c.

    – Stefan Lechner

    7. August 2017 um 20:10 Uhr

Benutzer-Avatar
Chux – Wiedereinsetzung von Monica

Gibt es eine Möglichkeit zu überprüfen, ob eine Zeichenfolge ein Float sein kann?

Ein Problem mit der sscanf(...,"%f") Ansatz ist auf Überlauf, was UB ist. Dennoch wird es allgemein gut gehandhabt.

Verwenden Sie stattdessen 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;
}

Benutzer-Avatar
chqrlie

Dies ist eine Variation des von dasblinkenlight geposteten Codefragments, die etwas einfacher und effizienter ist als strlen(str) könnte teuer werden:

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

Erläuterung: sscanf() kehrt zurück 1 wenn und nur wenn ein Float konvertiert wurde, gefolgt von einem optionalen Leerzeichen und keinem anderen Zeichen.

Benutzer-Avatar
Jonathan Leffler

Dieser Code basiert eng auf der Antwort von dasblinkenlight. Ich biete es als Denkanstoß an. Einige der Antworten, die es gibt, sind möglicherweise nicht das, was Sie wollten.

#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;
}

Beim Testen auf einem Mac mit macOS Sierra 10.12.6 und GCC 7.1.0 als Compiler erhalte ich die Ausgabe:

[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)

Besonders problematisch dürften die Hexadezimalzahlen sein. Die verschiedenen Formen von unendlich und keine Zahl könnten ebenfalls problematisch sein. Und das eine Beispiel mit einem Exponenten (255.12E456) überläuft float und erzeugt eine Unendlichkeit – ist das wirklich in Ordnung?

Die meisten der hier angesprochenen Probleme sind definitorisch – das heißt, wie definieren Sie, was akzeptabel sein soll. Aber beachte das strtod() würde alle gültigen Zeichenfolgen akzeptieren (und einige der ungültigen, aber andere Tests würden diese Probleme aufdecken).

Natürlich könnte der Testcode überarbeitet werden, um ein Array einer Struktur zu verwenden, die eine Zeichenfolge und das gewünschte Ergebnis enthält, und dies könnte verwendet werden, um die gezeigten Testfälle und alle Extras, die Sie hinzufügen, zu durchlaufen.

Die Besetzung auf das Ergebnis von strlen() vermeidet eine Kompilierungswarnung (Fehler, weil ich mit kompiliere -Werror) — comparison between signed and unsigned integer expressions [-Werror=sign-compare]. Wenn Ihre Saiten lang genug sind, dass das Ergebnis aus strlen() überläuft ein signiertes int, haben Sie andere Probleme damit, vorzugeben, dass es sich um gültige Werte handelt. OTOH, vielleicht möchten Sie mit 500 Stellen nach einem Dezimalkomma experimentieren – das ist gültig.

Dieser Code notiert die Kommentare zur Antwort von dasblinkenlight:

  • Führendes Leerzeichen im Format
  • Heikle besondere Umstände
  • Outline Fix – jetzt in der Antwort übernommen.

Benutzer-Avatar
0___________

Vielleicht das? Nicht sehr gut, aber kann den Job machen. Gibt -1 bei Fehler zurück, 0, wenn keine Konvertierungen durchgeführt wurden, und > 0, wenn Flags für konvertierte Zahlen gesetzt sind.

#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;
}

1175630cookie-checkGibt es eine Möglichkeit zu überprüfen, ob eine Zeichenfolge in C ein Float sein kann?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy