将纬度和经度转换为C中的浮点数

时间:2018-04-21 14:17:57

标签: c floating-point latitude-longitude

我试图将Lat和Lon坐标转换为4位小数浮点数。我有工作'代码,但它有点像%50的时间。任何人都可以帮助我或者有更好的设计来使它更精确吗?

 float sexag2decimal(char * deg){
    char *sdeg = malloc(sizeof(char)*3);
    char *smin = malloc(sizeof(char)*3);
    char *ssec = malloc(sizeof(char)*3);
    char *ssec2 = malloc(sizeof(char)*5);
    int size = strlen(deg);
    int m = 0;
    for(m = 0; m < size-1; m++){
        if(deg[m] >= 65 && deg[m] <= 122){
            return 0;
        }
    }
    float converted = 0;
    if(deg[0] == '0'){
        strcpy(&sdeg[0], &deg[1]);
        strcpy(&sdeg[1], &deg[2]);
        strcpy(&smin[0], &deg[4]);
        strcpy(&smin[1], &deg[5]);
        strcpy(&ssec[0], &deg[7]);
        strcpy(&ssec[1], &deg[8]);
        strcpy(&ssec2[0], &deg[10]);
        strcpy(&ssec2[1], &deg[11]);
        strcpy(&ssec2[2], &deg[12]);
        strcpy(&ssec2[3], &deg[13]);
    }else{
        if(deg[14] == 'W')
            return 0;
        strcpy(&sdeg[0], &deg[0]);
        strcpy(&sdeg[1], &deg[1]);
        strcpy(&smin[0], &deg[3]);
        strcpy(&smin[1], &deg[4]);
        strcpy(&ssec[0], &deg[6]);
        strcpy(&ssec[1], &deg[7]);
        strcpy(&ssec2[0], &deg[9]);
        strcpy(&ssec2[1], &deg[10]);
        strcpy(&ssec2[2], &deg[11]);
        strcpy(&ssec2[3], &deg[12]);
    }
    sdeg[2] = '\0';
    smin[2] = '\0';
    ssec[2] = '\0';
    ssec2[4] = '\0';
    converted = atoi(sdeg) + ((float)atoi(smin)/60.0) + (((float)atoi(ssec)+((float)atoi(ssec2))/10000)/3600.0);
    free(sdeg);
    free(smin);
    free(ssec);
    free(ssec2);
    return converted;
}

谢谢!

输入:

30-25-30.7140N,086-53-37.8590W

29-57-33.3000N,081-20-23.0000W

我的输出:

30.4252,-86.8939

29.9592,-81.3397

正确输出:

30.4252,-86.8938

29.9593,-81.3397

谢谢!

2 个答案:

答案 0 :(得分:1)

OP的代码遭受尝试写入外部分配的缓冲区。 @cyclaminist

float sexag2decimal(char * deg){
  char *sdeg = malloc(sizeof(char)*3);  // Too small for the entire string
  ...
    strcpy(&sdeg[0], &deg[0]); // UB: attempts to copy the _entire_ string to `deg`

当然OP意味着像

这样的代码
    // strcpy(&sdeg[0], &deg[0]);
    sdeg[0] = deg[0]; // Copy 1 char
  

它只有点像%50的时间

鉴于OP的代码在上述UB中幸存,观察到的输出与预期不同,部分原因是计算中累积舍入的结果:

converted = atoi(sdeg) + ((float)atoi(smin)/60.0) + 
    (((float)atoi(ssec)+((float)atoi(ssec2))/10000)/3600.0);

这导致(float)atoi(smin)/60.0(float)atoi(ssec2))/10000)(float)atoi(ssec2))/10000)/3600.0)以及3个添加中的每一个都进行四舍五入。

使用更高精度的数学运算,如double而不是float,将大大减少舍入效果,但不能消除它们

要提高准确性,请执行计算,不要使用整数数学进行舍入。角度的预期值,以万分之一秒为单位,需要大约36位数学。

long seconds = ((atoi(sdeg) * 60) + atoi(smin)) * 60 + atoi(ssec);
long myriad_seconds = (seconds * 10000) + atoi(ssec2);
  

...到小数点后4位浮点数

这个目标有点矛盾。 float通常使用二进制编码,因此不会产生十进制。听起来像OP想要打印到4位小数float。为此,代码会受到额外的舍入。

将确切的myriad_seconds转换为float会导致舍入错误。

// "29-57-33.3000N"
// myriad_seconds = 1078533000
// With infinite math, the quotient is 29.95925
float converted = myriad_seconds/(60.0f * 60 * 10000);
// The "best" `float` is just less than 29.95925
// converted = 29.959249777...

float转换为 4小数位文本会导致另一个舍入错误。

printf("%.4f\n", converted);
// "29.9592"

如果这是OP的目标,那么执行远远少于这个代码的舍入会大大改善但不会消除&#34;一次性&#34;一点点。

此任务的发起人巧妙地选择了xxx.xxxx5...附近的最终值。这些值对于计算路径敏感,只是稍微偏离&#34;。

为了抵消这种狡猾,考虑而不是返回float,返回一个度数为10,000的整数。有了错误/舍入免费myriad_seconds并且控制舍入到myriad_degrees,代码将始终达到预期的答案 - 代价是更复杂的源代码。

// Add signed half of 3600 to effect rounding (half way away from zero) before division.
long myriad_degrees = (myriad_seconds + (myriad_seconds < 0 ? -1 : 1) * 3600 / 2) / 3600;

printf("%s%ld.%04ld\n", myriad_degrees < 0 ? "-" : "", 
    labs(myriad_degrees) / 10000, labs(myriad_degrees) % 10000);

// Output 
29.9593

答案 1 :(得分:0)

(复制自己的评论)

首先,此功能浪费的大部分时间似乎都在malloc。您根本没有理由使用动态堆:只需分配像

这样的字符数组
char sdeg[3];

此外,每次从函数返回而不是从最后一行返回都会导致4次内存泄漏(free未被调用)。同样,您根本不需要malloc

其次,strcpy在这里不合适:只需复制角色即可。 strcpy需要NUL - 终止字符串并复制完整长度的源。这不是你需要的东西。

第三:如果你检查输入的健全性,排除字符范围65 ... 122不是个好主意。特别是,你拒绝它是'W'的情况,尽管后来你希望'W'作为一个可能的输入字符。

一般来说,我建议使用sscanf。有了它,您可以编写如下代码:

if (sscanf(deg, "%d-%d-%d.%d", &n1, &n2, &n3, &n4) == 4) {
    return some_formula_on_n1...n4;
}

最后,主要问题:float准确性绝对不足以满足您的期望。 1/10000秒(反过来是1/3600度)是1 / 6,480,000,000(全程度范围0 ... 180),远小于float相对误差(1 / 8,388,608) )。在所有值中切换到double。另外,请求打印至少10位有效数字。 6个默认值太小。

您也可以考虑@chux描述的方法仅用于整数数学。与二进制浮动不同,它绝对没有舍入效果和难以证明的准确性,尽管更复杂(和繁琐)。