划分双打时意外失去精度

时间:2009-03-30 09:31:17

标签: c++ double precision

我有一个函数getSlope,它接受4个双精度参数并返回另一个使用这个给定参数计算的双精度,如下所示:

double QSweep::getSlope(double a, double b, double c, double d){
double slope;
slope=(d-b)/(c-a);
return slope;
}

问题是当使用参数调用此函数时,例如:

getSlope(2.71156, -1.64161, 2.70413, -1.72219);

返回的结果是:

10.8557

这对我的计算来说不是一个好结果。 我使用Mathematica计算了斜率,相同参数的斜率结果为:

10.8452

或更多精度数字:

10.845222072678331.

我的程序返回的结果在我的进一步计算中并不好。 此外,我不明白该程序如何从10.845222072678331开始返回10.8557(假设这是该部门的近似结果)? 如何才能为我的部门取得好成绩?

提前谢谢你, madalina


我使用命令行打印结果:

std::cout<<slope<<endl;

可能是我的参数可能不好,因为我从另一个程序中读取它们(计算图形;从我的图形中读取这些参数后,我刚刚显示它们以查看它们的值但是可能显示的矢量有计算值的内部精度不一样。我不知道它真的很奇怪。出现一些数值误差..)

当计算我正在读取我的参数的图形时,使用一些用C ++编写的数字库(带有模板)。没有OpenGL用于此计算。

谢谢你, madalina

8 个答案:

答案 0 :(得分:7)

我尝试使用float而不是double,因此得到10.845110。它仍然看起来比madalina的结果更好。

编辑:

我想我知道你为什么得到这个结果。如果从其他地方获得a,b,c和d参数并打印它,它会为您提供舍入值。然后,如果你把它放到Mathemtacia(或calc;))它会给你不同的结果。

我尝试改变你的一个参数。当我这样做时:

double c = 2.7041304;

我得到10.845806。我只添加0.0000004到c! 所以我认为你的“错误”不是错误。以更高的精度打印a,b,c和d,然后将它们放到Mathematica。

答案 1 :(得分:5)

您的项目中是否使用DirectX或OpenGL?如果是这样,他们可以关闭双精度,你会得到奇怪的结果。

您可以使用

检查精度设置
std::sqrt(x) * std::sqrt(x)

结果必须非常接近x。 我很久以前就遇到过这个问题,花了一个月检查所有公式。但后来我找到了

D3DCREATE_FPU_PRESERVE

答案 2 :(得分:5)

以下代码:

#include <iostream>
using namespace std;

double getSlope(double a, double b, double c, double d){
    double slope;
    slope=(d-b)/(c-a);
    return slope;
}

int main( ) {
    double s = getSlope(2.71156, -1.64161, 2.70413, -1.72219);
    cout << s << endl;
}

使用g ++给出10.8452的结果。如何在代码中打印出结果?

答案 3 :(得分:3)

这里的问题是(c-a)很小,因此在这个例子中浮点运算中固有的舍入误差被放大了。一般的解决方案是重新修改你的等式,这样你就不会用一个小数字来划分,我不知道你会怎么做。

编辑:

Neil对这个问题的评论是正确的,我使用Doubles计算了VB中的答案并得到了与mathematica相同的答案。

答案 4 :(得分:2)

您获得的结果与32位算术一致。如果不了解您的环境,就不可能建议做什么。

假设显示的代码正在运行,即你没有将任何内容转换为字符串或浮点数,那么C ++中就没有修复。它超出了您展示的代码,并且取决于环境。

帕特里克麦克唐纳和特雷布提高了输入的准确性和a-c上的错误,我想我会看一下。查看舍入误差的一种技术是区间运算,它使得上限和下限值表示显式(它们隐含在浮点数中,并且固定为表示的精度)。通过将每个值视为上限和下限,并通过表示中的错误扩展边界(对于双值x约为x * 2 ^ -53),您将得到一个结果,该结果给出了下限和上限。考虑到最坏情况的精度误差,值的准确性。

例如,如果您的值在[1.0,2.0]范围内并从中减去[0.0,1.0]范围内的值,那么结果必须位于[低于(0.0),上面的范围内( 2.0)]最小结果为1.0-1.0,最大值为2.0-0.0。 belowabove等同于floor和ceiling,但是对于下一个可表示的值而不是整数。

使用代表最坏情况双舍入的区间:

getSlope(
 a = [2.7115599999999995262:2.7115600000000004144], 
 b = [-1.6416099999999997916:-1.6416100000000002357], 
 c = [2.7041299999999997006:2.7041300000000005888], 
 d = [-1.7221899999999998876:-1.7221900000000003317])
(d-b) = [-0.080580000000000526206:-0.080579999999999665783]
(c-a) = [-0.0074300000000007129439:-0.0074299999999989383218]

to double precision [10.845222072677243474:10.845222072679954195]

因此尽管c-aca相比较小,但与双舍入相比,它仍然很大,所以如果你使用最差的可想象的双精度舍入,那么你可以相信这个价值要精确到12个数字 - 10.8452220727。你已经失去了一些双精度的数字,但你的工作仍然超过你的输入意义。

但是如果输入仅对数字有效数字准确,那么输入范围将是[2.711555 + /-ε],而不是2.71156 +/- eps的双倍值,因此得到结果:

getSlope(
 a = [2.711555:2.711565], 
 b = [-1.641615:-1.641605], 
 c = [2.704125:2.704135], 
 d = [-1.722195:-1.722185])
(d-b) = [-0.08059:-0.08057]
(c-a) = [-0.00744:-0.00742]

to specified accuracy [10.82930108:10.86118598]

这是一个更广泛的范围。

但是你必须不遗余力地追踪计算的准确性,浮点中固有的舍入误差在这个例子中并不重要 - 它最精确到12个数字,最坏情况是双精度舍入。

另一方面,如果您的输入仅为6位数,则无论您获得10.8557还是10.8452,实际上并不重要。两者均在[10.82930108:10.86118598]内。

答案 5 :(得分:1)

更好打印出论点。如你所知,当你以十进制表示法传输参数时,你将失去每一个的精度。问题是1/5是二进制的无限级数,所以例如0.2变为.001001001 ....此外,将二进制浮点数转换为十进制文本表示时,小数将被切断。

接下来,有时编译器选择速度超过精度。这应该是一个文档化的编译器开关。

答案 6 :(得分:0)

Patrick似乎是正确的(c-a)是主要原因:

  

d-b = -1,72219 - (-1,64161)= - 0,08058

     

c-a = 2,70413 - 2,71156 = -0,00743

     

S =(d-b)/(c-a)= -0,08058 / -0,00743 = 10,845222

你从六位精度开始,通过减法你得到一个减少到3和四位数。我最好的猜测是你放松了额外的精度,因为数字-0,00743不能在双精度中表示为exaclty。尝试使用具有更高精度的中间变量,如下所示:

double QSweep::getSlope(double a, double b, double c, double d)
{
    double slope;
    long double temp1, temp2;

    temp1 = (d-b);
    temp2 = (c-a);
    slope = temp1/temp2;

    return slope;
}

答案 7 :(得分:-1)

虽然正在进行的学术讨论非常适合了解编程语言的局限性,但您可能会发现问题的最简单解决方案是arbitrary precision arithmetic的数据结构。

这会有一些开销,但你应该能够找到具有相当可靠的准确性的东西。