浮点精度——c++中的向量加法并不完全正确

floating accuracy - Vector addition in C++ is not coming out exactly right

本文关键字:向量 完全正确 精度 c++      更新时间:2023-10-16

我正在尝试在我的c++程序中添加一个向量到另一个向量,并且添加结果不准确。

这是vector类(使用cmath库):

class Vec{
    float dir, mag;
    public:
        Vec(float dir, float mag){
            this->dir = dir;
            this->mag = mag;
        }
        float getX(){
            return cos(dir)*mag;
        }
        Vec operator + (Vec v2){
            float triangleX = cos(dir)*mag+cos(v2.dir)*v2.mag;
            float triangleY = sin(dir)*mag+sin(v2.dir)*v2.mag;
            return Vec(atan2(triangleY, triangleX), sqrt(pow(triangleX,2)+pow(triangleY,2)));
    }
};

主要功能如下:

int main(){
    Vec v1(0, 3); // 0º
    Vec v2(3.14159265/2, 3); // 90º
    Vec v3(3.14159265, 3); // 180º
    std::cout.precision(15);
    std::cout<<"v1: "<<v1.getX()<<std::endl;
    std::cout<<"v1+v2: "<<(v1+v2).getX()<<std::endl;
    std::cout<<"v1+v3: "<<(v1+v3).getX()<<std::endl;
    return 0;
}

输出:

v1: 3
v1+v2: 2.99999976158142
v1+v3: 1.98007097372034e-014

可以看到,第一个输出v1很好。

第二个输出是0度和90度的相加(一个不应该影响x分量的角度),他的x分量接近3,但不完全是3。

第三个输出是两个大小相同的相反向量的相加,它应该是0,但它不是这里显示的。

是什么导致了这些奇怪的添加,我如何使它们准确?

v1 + v3几乎是0.0,代码工作正常

为什么不完全是0.0

,因为有些数字不能精确地表示为双精度。见:C/c++: 1.00000 <= 1.0f = False给出一些解释。

还有:Pi是无理数,不能在任何为自然数的进制中精确表示。

因此,任何涉及Pi的计算都不可能完全精确。

和:sin, cos, sqrt通常都实现为不返回完全精确结果的算法,例如近似数值算法。

您遇到的基本问题是float和您正在使用的pi的值的有限精度之一。移动到double将有所帮助,并且由于您已经包括<cmath>,您应该使用M_PI的值,并且至少精确到double的精度。尽管如此,你仍然不能用这种方法得到确切的答案。(alain的答案很好地解释了原因。)

有一些可以改进的地方。一个是使用c++ 11特性的巧妙技巧,即"用户定义的字符串字面量"。如果您将此定义添加到代码中:

constexpr long double operator"" _deg(long double deg) {
    return deg*M_PI/180;
}

您现在可以将_deg附加到任何长双字量,它将在编译时自动转换为弧度。然后您的main函数看起来像这样:

int main(){
    Vec v1(0.0_deg, 3); 
    Vec v2(90.0_deg, 3); 
    Vec v3(180.0_deg, 3); 
    // ...
}

接下来你可以做的是存储x和y坐标,只在需要的时候做三角运算。那个版本的Vec可能是这样的:

class Vec{
    double x,y;
    public:
        Vec(double dir, double mag, bool cartesian=false) : x(dir), y(mag) {
            if (!cartesian) {
                x = mag*cos(dir);
                y = mag*sin(dir);
            }
        }
        double getX() const {
            return x;
        }
        Vec operator + (const Vec &v2){
            return Vec(x+v2.x, y+v2.y, true);
        }
}

注意,我已经为构造函数创建了一个bool值,它告诉输入是一个大小和方向还是一个x和y值。还要注意,getX()被声明为const,因为它不会改变Vec,并且operator+的参数也是const引用,原因相同。当我在我的机器(64位机器)上进行这些更改时,我得到以下输出:

v1: 3
v1+v2: 3
v1+v3: 0