C ++中的向量加法并不完全正确[重复]

Posted

技术标签:

【中文标题】C ++中的向量加法并不完全正确[重复]【英文标题】:Vector addition in C++ is not coming out exactly right [duplicate] 【发布时间】:2014-05-04 13:04:57 【问题描述】:

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

这是矢量类(使用 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。

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

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

【问题讨论】:

我建议使用坐标 (x,y) 来轻松生活。 推荐使用双精度,不仅因为它更准确,而且大多数现代架构都针对双精度而非浮点数进行了优化 @LưuVĩnhPhúc 你知道使用doublefloat 更快的平台吗? Afaik 所有平台都支持两者,大多数仍然是 float 的两倍。并且float 的内存占用也只有double 的一半,因此在所有平台上使用float 的内存绑定计算将执行得更快。不过,我完全同意你应该将 double 用作默认值。 @cmaster 我听到很多人说他们看到 double 比 float 快。实际上我以前没有做过基准测试,但是这个网站上的大多数答案都表明现代平台上的双精度数至少与浮点数一样快。 ***.com/questions/1074474/…***.com/questions/4584637/… @LưuVĩnhPhúc 阅读链接上的答案,我找不到任何人声称双打 比浮动快。没错,因为那是错误的。在我知道的所有平台上,情况恰恰相反。但是我不了解所有平台,所以我不能排除这种疯狂的 CPU 比浮点数更快地加倍的可能性。因此,在某些(深奥的)平台上,双打可能可能更快。但这正是我的问题:您知道双打速度更快的特定平台吗? 【参考方案1】:

v1 + v3 几乎是0.0,代码运行正常。

为什么不完全是0.0

因为有些数字不能完全表示为双精度数。 一些解释见:C/C++: 1.00000 <= 1.0f = False。

另外:Pi 是一个irrational 数字,不能以自然数的任何基数精确表示。

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

并且:sincossqrt 通常都是作为不返回完全精确结果的算法实现的,例如近似数值算法。

【讨论】:

我打算在您编辑帖子之前对此发表评论。此外,不建议使用这样的文字而不是 PI 常量 如果我错了请纠正我,但我认为sin & co。仅在您打开快速数学编译器标志时是近似值? @cmaster 我不知道它们在哪些库/使用哪些标志是近似的,但更快的实现之一是具有线性插值的表。 (这实际上不是近似的,但也不是完全精确的) @cmaster 我编辑了我的答案,使其更加准确。感谢您的意见。 @alain:为了迂腐,可以将 pi 准确地表示为 1 基 pi。【参考方案2】:

您遇到的基本问题是float 的精度有限和您正在使用的pi 的值之一。移动到double 会有所帮助,因为您已经包含&lt;cmath&gt;,您应该使用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

【讨论】:

以上是关于C ++中的向量加法并不完全正确[重复]的主要内容,如果未能解决你的问题,请参考以下文章

C中的算术级数不是正确的结果[重复]

将指针向量传递给c ++中的函数[重复]

用向量填充c ++中的简单动态数组[重复]

从基类c ++的向量中的派生类调用虚拟方法[重复]

如何标准化r中的向量[重复]

Vector中的C ++字符串输入[重复]