about_sqrt_and_acos

转移自老blog

关于sqrt对负数开根号acos对大于1的数计算反三角函数值的问题

空间里面有一个实心球,两个点,问两点不经过实心球的路径的最小值.
题目意思很简单,我们可能会用到余弦定理,但是,余弦定理有误差,我们可能会得到一些奇怪的数字,浮点数的误差导致 余弦定理计算出来了小于-1或者大于1的其他数字,当我们对这样的数字进行反三角函数运算时,会得到nan,当然sqrt 有时候也会碰到对负数开方,于是我们要重写这两个函数
#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-12;
const double INF = 1e18;
const double PI = acos(-1.0);
double ACOS(double x)
{
    if(x>1)
        x=1.0;
    if(x<-1)
        x=-1.0;
    return acos(x);
}
double SQRT(double x)
{
    if(x<0)
        return 0;
    return sqrt(x);
}

struct Point3D
{
    double x, y, z;
    Point3D() {};
    Point3D(double xx, double yy,double zz)
    {
        x = xx;
        y = yy;
        z = zz;
    }
};
typedef struct Point2D
{
    Point2D() {};
    double x;
    double y;
    Point2D(double xx, double yy)
    {
        x = xx;
        y = yy;
    }
} Point;

struct Circle3D
{
    Point3D o;
    double r;
};
struct Circle
{
    Point2D o;
    double r;
};


struct Line
{
    Point s, e;
    Line() {};
    Line(Point a, Point b)
    {
        s = a;
        e = b;
    };
};


double dis(Point a, Point b)
{
    return SQRT(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}


double dis(Point3D a, Point3D b)
{
    return SQRT(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) + pow(a.z - b.z, 2));
}

double solve(Point3D s, Point3D t, Circle3D o)
{
    double so=dis(s,o.o);
    double to=dis(t,o.o);
    double st=dis(s,t);
    double sita_sot=ACOS((so*so+to*to-st*st)/2/so/to);
    double sita_sto=ACOS((st*st+to*to-so*so)/2/st/to);
    double sita_tso=ACOS((st*st+so*so-to*to)/2/st/so);


    double p=(so+to+st)/2;
    double d=SQRT(p*(p-so)*(p-to)*(p-st))*2/st;


    if(sita_sto<PI/2&&sita_tso<PI/2&&d<o.r)
    {
        double sita_soa1=ACOS(o.r/so);
        double sita_toa2=ACOS(o.r/to);

        double sita=sita_sot-sita_soa1-sita_toa2;

        return sita*o.r + so*sin(sita_soa1) + to*sin(sita_toa2);

    }
    else
    {
        return dis(s, t);
    }
}


double solve(Point s, Point t, Circle o)
{
    double so=dis(s,o.o);
    double to=dis(t,o.o);
    double st=dis(s,t);
    double sita_sot=ACOS((so*so+to*to-st*st)/2/so/to);
    double sita_sto=ACOS((st*st+to*to-so*so)/2/st/to);
    double sita_tso=ACOS((st*st+so*so-to*to)/2/st/so);


    double p=(so+to+st)/2;
    double d=SQRT(p*(p-so)*(p-to)*(p-st))*2/st;


    if(sita_sto<PI/2&&sita_tso<PI/2&&d<o.r)
    {
        double sita_soa1=ACOS(o.r/so);
        double sita_toa2=ACOS(o.r/to);

        double sita=sita_sot-sita_soa1-sita_toa2;

        return sita*o.r + so*sin(sita_soa1) + to*sin(sita_toa2);

    }
    else
    {
        return dis(s, t);
    }
}
double solve(double a,double b,double c,double r)
{
    Point s, t;
    Circle o;
    double arcsita = (pow(b, 2) + pow(c, 2) - pow(a, 2)) / 2 / b / c;
    double sita = ACOS(arcsita);

    t.x = c * cos(sita);
    t.y = c * sin(sita);

    o.o.x = 0;
    o.o.y = 0;
    o.r = r;

    s.x = b;
    s.y = 0;

    return solve(s, t, o);
}


int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        Circle3D o;
        Point3D s, t;
        scanf("%lf%lf%lf%lf", &o.o.x, &o.o.y, &o.o.z,&o.r);
        scanf("%lf%lf%lf", &s.x, &s.y, &s.z);
        scanf("%lf%lf%lf", &t.x, &t.y, &t.z);
        double a = dis(s, t);
        double b = dis(s, o.o);
        double c = dis(t, o.o);

        if(fabs(a)<eps)
        {
            printf("0.0000000000\n");
        }
        else
        {
            printf("%.8lf\n", solve(a,b,c,o.r));
        }
    }
}