使用 sscanf 读取 double 时忽略 'E'

Ignore 'E' when reading double with sscanf

我有输入 "(50.1003781N, 14.3925125E)" 。这些是纬度和经度。

我想用

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

但是当%lf看到数字后面的E时,它会消耗它并以指数形式将其存储为数字。有没有办法禁用它?

您可以尝试读取所有字符串,然后将 E 替换为另一个字符

首先使用

处理字符串
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 方法,而不是 C++ 方法。但是,通过使用 sscanf() 你实际上是在使用 C 方法。

我认为您需要进行手动解析,可能需要使用 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))
$

基本上,您将跳过白色 space,检查 (strtod() 数字,检查 NS 或更低大小写版本,逗号,strtod() 数字,检查 WE,检查 ) 可能允许白色 space 在它之前。

升级代码,具有基于strtod()等人的适度通用strtolatlon()功能。 'const cast' 在 strtod() 等函数中是必需的,它采用 const char * 输入和 return 通过 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 的使用简化了错误处理。没有它你也可以写代码;如果我有更多时间,也许我会升级它。但是,由于诊断错误的位置有 7 个,报告错误需要 4 行,goto 提供了合理的清晰度,没有太多重复。

请注意,strtolatlon() 函数通过其 return 值明确识别错误;成功与否无需猜测。如果您希望确定错误所在,可以增强错误报告。但这样做取决于您的错误报告基础架构,而这并非如此。

此外,strtolatlon() 函数将接受一些奇怪的格式,例如 (+0.501003781E2N, 143925125E-7E)。如果这是一个问题,您将需要编写自己的 strtod() 的更复杂的变体,它只接受定点表示法。另一方面,有一个 meme/guideline "Be generous in what you accept; be strict in what you produce"。这意味着这里的内容或多或少是可以的(在 N、S、E、W 字母、逗号和右括号之前允许可选的白色 space 可能会很好)。相反的代码,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;
}

请注意,度数小数点后 7 位的 1 个单位 (10-7 ˚) 对应于地面上大约一厘米(沿子午线定向;距离表示为当然,沿纬线的度数随纬度而变化。