acos()结果不好,C中有两个关闭点

时间:2013-12-31 12:00:37

标签: c math.h rounding-error

我正在制作一个函数,使用它们的纬度/经度(以度为单位而不是弧度)和余弦的球面律来计算两点的距离。我遇到的问题是,由于函数acos()中的舍入误差,当两点非常接近时,我得到的结果远远不够好。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct{
 double lat;
 double lon;
} point;

#define DEG_TO_RAD 0.017453292519943295769236907684886
#define EARTH_RADIUS_IN_METERS 6372797.560856

double distance(point a, point b) {
  double arg=sin(a.lat * DEG_TO_RAD) * sin(b.lat * DEG_TO_RAD) + cos(a.lat * DEG_TO_RAD) * cos(b.lat * DEG_TO_RAD) * cos((a.lon-b.lon) * DEG_TO_RAD);
  if(arg>1) arg=1;
  else if (arg<-1) arg=-1;
  printf("arg=%.12f acos(arg)=%.12f\n",arg, acos(arg));    //to see the problem
  return acos(arg) * EARTH_RADIUS_IN_METERS;
}

int main(){
  point p1,p2;

  p1.lat=63.0;
  p1.lon=27.0;
  p2.lat=p1.lat;
  p2.lon=p1.lon;

  printf("dist=%.8f\n",distance(p1,p2));

  return 0;
}

输出

arg=1.000000000000 acos(arg)=0.000000014901
dist=0.09496208

正如你所看到的,当它计算acos()它应该给出零但是它确实会给出一些在乘以地球半径后得到极大放大的误差。当两点不相等但非常接近时,也会发生这种情况。如果有任何用处,我的纬度和经度数据最多有7个十进制数字。

3 个答案:

答案 0 :(得分:5)

您从acos获得的结果非常好:问题是arg的计算总是会有一个小错误,并返回稍微偏离的值。当两点相等或非常接近时,结果小于1,例如1-10 -16 。如果你看the graph of acos(x),你会发现它在x = 1处几乎是垂直的,这意味着即使arg中的最轻微错误也会对相对误差产生巨大影响。换句话说,算法在数值上是不稳定的。

您可以使用haversine formula获得更好的结果。

答案 1 :(得分:4)

这正是extended precision的用途:以比用于参数和结果的double精度更高的精度计算中间结果。

我改变了你的程序:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>

typedef struct{
 double lat;
 double lon;
} point;

#define DEG_TO_RAD 0.017453292519943295769236907684886L
#define EARTH_RADIUS_IN_METERS 6372797.560856L

double distance(point a, point b) {
  long double arg=sinl(a.lat * DEG_TO_RAD) * sinl(b.lat * DEG_TO_RAD) + cosl(a.lat * DEG_TO_RAD) * cosl(b.lat * DEG_TO_RAD) * cosl((a.lon-b.lon) * DEG_TO_RAD);
  if(arg>1) arg=1;
  else if (arg<-1) arg=-1;
  printf("arg=%.20Le acos(arg)=%.20Le\n",arg, acosl(arg));
  return acosl(arg) * EARTH_RADIUS_IN_METERS;
}

int main(){
  point p1,p2;

  p1.lat=63.0;
  p1.lon=27.0;
  p2.lat=p1.lat;
  p2.lon=p1.lon;

  printf("precision of long double:%Le\n", LDBL_EPSILON);

  printf("dist=%.8f\n",distance(p1,p2));

  return 0;
}

通过这些更改,在为long double提供扩展精度的编译器上,结果符合预期:

precision of long double:1.084202e-19
arg=1.00000000000000000000e+00 acos(arg)=0.00000000000000000000e+00
dist=0.00000000

编辑:

这是一个使用GCC的quadmath库进行中间结果的版本。它需要最近的GCC。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <quadmath.h>

typedef struct{
 double lat;
 double lon;
} point;

#define DEG_TO_RAD (M_PIq / 180)
#define EARTH_RADIUS_IN_METERS ((__float128)6372797.560856L)

double distance(point a, point b) {
  __float128 arg=sinq(a.lat * DEG_TO_RAD) * sinq(b.lat * DEG_TO_RAD) + cosq(a.lat * DEG_TO_RAD) * cosq(b.lat * DEG_TO_RAD) * cosq((a.lon-b.lon) * DEG_TO_RAD);
  if(arg>1) arg=1;
  else if (arg<-1) arg=-1;
  printf("arg=%.20Le acos(arg)=%.20Le\n",(long double)arg, (long double)acosq(arg));
  return acosq(arg) * EARTH_RADIUS_IN_METERS;
}

int main(){
  point p1,p2;

  p1.lat=63.0;
  p1.lon=27.0;
  p2.lat=p1.lat;
  p2.lon=p1.lon;

  printf("dist=%.8f\n",distance(p1,p2));

  return 0;
}

我编译并运行:

$ gcc-206231/bin/gcc t.c -lquadmath && LD_LIBRARY_PATH=gcc-206231/lib64 ./a.out 

答案 2 :(得分:0)

在这种情况下,通常最好尝试重新解决问题 避免使用 x 的acos( x )等条件非常严格的公式。这是 在球体上进行距离计算的情况下,这是一个很好的问题 Great-circle page上提供了更好的配方 维基百科。这些与普通的短距离提供高精度 双精度算术。