我试图将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], °[1]);
strcpy(&sdeg[1], °[2]);
strcpy(&smin[0], °[4]);
strcpy(&smin[1], °[5]);
strcpy(&ssec[0], °[7]);
strcpy(&ssec[1], °[8]);
strcpy(&ssec2[0], °[10]);
strcpy(&ssec2[1], °[11]);
strcpy(&ssec2[2], °[12]);
strcpy(&ssec2[3], °[13]);
}else{
if(deg[14] == 'W')
return 0;
strcpy(&sdeg[0], °[0]);
strcpy(&sdeg[1], °[1]);
strcpy(&smin[0], °[3]);
strcpy(&smin[1], °[4]);
strcpy(&ssec[0], °[6]);
strcpy(&ssec[1], °[7]);
strcpy(&ssec2[0], °[9]);
strcpy(&ssec2[1], °[10]);
strcpy(&ssec2[2], °[11]);
strcpy(&ssec2[3], °[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
谢谢!
答案 0 :(得分:1)
OP的代码遭受尝试写入外部分配的缓冲区。 @cyclaminist
float sexag2decimal(char * deg){
char *sdeg = malloc(sizeof(char)*3); // Too small for the entire string
...
strcpy(&sdeg[0], °[0]); // UB: attempts to copy the _entire_ string to `deg`
当然OP意味着像
这样的代码 // strcpy(&sdeg[0], °[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描述的方法仅用于整数数学。与二进制浮动不同,它绝对没有舍入效果和难以证明的准确性,尽管更复杂(和繁琐)。