使用 sscanf 读取双精度时忽略"E"

Ignore 'E' when reading double with sscanf

本文关键字:sscanf 读取 双精度 使用      更新时间:2023-10-16

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

我想解析这个

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

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

我认为您需要进行手动解析,可能使用strtod(). 这表明,当遇到尾随E时,strtod()表现得很理智(至少在带有GCC 4.9.1的Mac OS X 10.10.3上 - 但可能无处不在)。

#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: %sn", 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()数字,检查NS或小写版本,逗号,strtod()数字,检查WE,检查)可能允许在它之前留空格。

升级的代码,具有基于strtod()等人的中等通用strtolatlon()函数。 "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: %sn", 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 failedn");
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()函数通过其返回值显式标识错误;无需猜测它是否成功。 如果您希望确定错误的位置,则可以增强错误报告。 但是,这样做取决于您的错误报告基础结构,而这不会。

此外,strtolatlon()函数将接受一些奇球格式,例如(+0.501003781E2N, 143925125E-7E). 如果这是一个问题,你需要编写你自己的更挑剔的strtod()变体,只接受定点符号。 另一方面,有一个模因/指导方针"在你接受的东西上要慷慨;严格要求生产"。 这意味着这里的内容或多或少是可以的(在 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;
}

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

首先使用

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 方法。

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