Игнорировать "E" при чтении с помощью sscanf

У меня есть вход, такой как "(50.1003781N, 14.3925125E)". Это широта и долгота.

Я хочу проанализировать это с помощью

sscanf(string,"(%lf%c, %lf%c)",&a,&b,&c,&d);

но когда %lf видит E после номера, он потребляет его и сохраняет его как число в экспоненциальной форме. Есть ли способ отключить это?

Ответы

Ответ 1

Думаю, вам нужно будет вручную разобрать, возможно, используя strtod(). Это показывает, что strtod() ведет себя разумно, когда он встречается с трейлинг файлом E (по крайней мере, в Mac OS X 10.10.3 с GCC 4.9.1 - но, вероятно, везде).

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

int main(void)
{
    const char latlong[] = "(50.1003781N, 14.3925125E)";
    char *eptr;
    double d;
    errno = 0;      // Necessary in general, but probably not necessary at this point
    d = strtod(&latlong[14], &eptr);
    if (eptr != &latlong[14])
        printf("PASS: %10.7f (%s)\n", d, eptr);
    else
        printf("FAIL: %10.7f (%s) - %d: %s\n", d, eptr, errno, strerror(errno));

    return 0;
}

Компиляция и запуск:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror latlong.c -o latlong
$ ./latlong
PASS: 14.3925125 (E))
$

В принципе, вы пропустите пробел, проверьте число (, strtod() на число, проверьте для N или S или версии в нижнем регистре, запятую, strtod() номер, установите флажок W или E, проверьте ), возможно, разрешите пробел перед ним.

Модернизированный код с умеренно общей функцией strtolatlon() на основе strtod() et al. "Const cast" необходим в таких функциях, как strtod(), которые принимают вход const char * и возвращают указатель в эту строку с помощью переменной char **eptr.

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CONST_CAST(type, value) ((type)(value))

extern int strtolatlon(const char *str, double *lat, double *lon, char **eptr);

int strtolatlon(const char *str, double *lat, double *lon, char **eptr)
{
    const char *s = str;
    char *end;
    while (isspace(*s))
        s++;
    if (*s != '(')
        goto error;
    *lat = strtod(++s, &end);
    if (s == end || *lat > 90.0 || *lat < 0.0)
        goto error;
    int c = toupper((unsigned char)*end++);
    if (c != 'N' && c != 'S')  // I18N
        goto error;
    if (c == 'S')
        *lat = -*lat;
    if (*end != ',')
        goto error;
    s = end + 1;
    *lon = strtod(s, &end);
    if (s == end || *lon > 180.0 || *lon < 0.0)
        goto error;
    c = toupper((unsigned char)*end++);
    if (c != 'W' && c != 'E')  // I18N
        goto error;
    if (c == 'E')
        *lon = -*lon;
    if (*end != ')')
        goto error;
    if (eptr != 0)
        *eptr = end + 1;
    return 0;

error:
    if (eptr != 0)
        *eptr = CONST_CAST(char *, str);
    errno = EINVAL;
    return -1;
}

int main(void)
{
    const char latlon1[] = "(50.1003781N, 14.3925125E)";
    const char latlon2[] = "   (50.1003781N, 14.3925125E) is the position!";
    char *eptr;
    double d;
    errno = 0;      // Necessary in general, but Probably not necessary at this point
    d = strtod(&latlon1[14], &eptr);
    if (eptr != &latlon1[14])
        printf("PASS: %10.7f (%s)\n", d, eptr);
    else
        printf("FAIL: %10.7f (%s) - %d: %s\n", d, eptr, errno, strerror(errno));

    printf("Converting <<%s>>\n", latlon2);
    double lat;
    double lon;
    int rc = strtolatlon(latlon2, &lat, &lon, &eptr);
    if (rc == 0)
        printf("Lat: %11.7f, Lon: %11.7f; trailing material: <<%s>>\n", lat, lon, eptr);
    else
        printf("Conversion failed\n");

    return 0;
}

Пример вывода:

PASS: 14.3925125 (E))
Converting <<   (50.1003781N, 14.3925125E) is the position!>>
Lat:  50.1003781, Lon: -14.3925125; trailing material: << is the position!>>

Это не всестороннее тестирование, но оно иллюстративно и близко к качеству продукции. Возможно, вам придется беспокоиться о бесконечностях, например, в истинном производственном коде. Я не часто использую goto, но это случай, когда использование goto упростило обработку ошибок. Вы можете написать код без него; если бы у меня было больше времени, возможно, я бы его обновил. Тем не менее, в семи местах, где диагностируются ошибки, и 4 строки, необходимые для сообщения об ошибке, goto обеспечивает разумную четкость без большого повторения.

Обратите внимание, что функция strtolatlon() явно идентифицирует ошибки через возвращаемое значение; нет необходимости угадывать, удалось ли это или нет. Вы можете улучшить отчет об ошибках, если хотите определить, где ошибка. Но это зависит от вашей инфраструктуры отчетности об ошибках таким образом, что это не так.

Кроме того, функция strtolatlon() примет некоторые форматы нечетного шара, такие как (+0.501003781E2N, 143925125E-7E). Если это проблема, вам нужно написать свой собственный более суетливый вариант strtod(), который принимает только фиксированную нотацию. С другой стороны, существует meme/guideline "Будьте щедры в том, что вы принимаете, будьте строгими в том, что вы производите". Это означает, что здесь более или менее нормально (может быть полезно разрешить дополнительное пустое пространство перед буквами N, S, E, W, запятой и закрывающей скобкой). Обратный код latlontostr() или fmt_latlon()strtolatlon() переименованный в scn_latlon(), возможно) или что-то еще, будет осторожен в том, что он производит, только генерирует буквы верхнего регистра и всегда использует фиксированный формат и т.д..

int fmt_latlon(char *buffer, size_t buflen, double lat, double lon, int dp)
{
    assert(dp >= 0 && dp < 15);
    assert(lat >=  -90.0 && lat <=  90.0);
    assert(lon >= -180.0 && lon <= 180.0);
    assert(buffer != 0 && buflen != 0);
    char ns = 'N';
    if (lat < 0.0)
    {
        ns = 'S';
        lat = -lat;
    }
    char ew = 'W';
    if (lon < 0.0)
    {
        ew = 'E';
        lon = -lon;
    }
    int nbytes = snprintf(buffer, buflen, "(%.*f%c, %.*f%c)", dp, lat, ns, dp, lon, ew);
    if (nbytes < 0 || (size_t)nbytes >= buflen)
        return -1;
    return 0;
}

Обратите внимание, что 1 единица в 7 десятичных разрядов степени (10 -7 ˚) соответствует около сантиметра на земле (ориентирована вдоль меридиана, расстояние, представленное степенью вдоль параллели широты варьируется в зависимости от широты, конечно).

Ответ 2

Сначала обработать строку, используя

char *p;
while((p = strchr(string, 'E')) != NULL) *p = 'W';
while((p = strchr(string, 'e')) != NULL) *p = 'W';

// scan it using your approach

sscanf(string,"(%lf%c, %lf%c)",&a,&b,&c,&d);

// get back the original characters (converted to uppercase).

if (b == 'W') b = 'E';    
if (d == 'W') d = 'E';

strchr() объявляется в заголовке C <string.h>.

Примечание. Это действительно подход C, а не подход С++. Но, используя sscanf(), вы действительно используете подход C.

Ответ 3

Вы можете попробовать прочитать всю строку, а затем заменить E на другой chracter