【问题标题】:Difference in precision when scaling double in C++在 C++ 中缩放双倍时的精度差异
【发布时间】:2015-07-30 12:35:37
【问题描述】:

是否需要缩放双精度值,或者在使用真实世界值时它们是否更精确?使用 double 进行计算以获得最佳精度的最佳范围是哪个?

即我定义了三次贝塞尔曲线。我应该对三次曲线使用真实世界的位置值,还是应该在计算时使用标准化大小的值,然后在我想读取真实世界的值时按比例放大它们?

我举个例子,看看这段代码:

void CCubic::getTAtDistance(const CCubicPoint& pFrom, const double& distance, double& t)
{

    CPoint pEnd = getP(t);

    double cLength = (pEnd - pFrom).getLength();
    int compare = GLOBAL::DoubleCompare(cLength, distance);
    if(compare > 0)
    {
        t-=(t - pFrom.t)*0.5;
        getTAtDistance(pFrom, distance, t);
    }
    else if(compare < 0)
    {
        t+=(t - pFrom.t)*0.5;
        getTAtDistance(pFrom, distance, t);
    }//else if
}

此方法计算三次曲线上一个点与三次曲线上另一个点的距离。

  • pFrom 是计算距离的点。
  • t 将被增量计算并定义新的 增量完成时曲线上指定距离处的点。
  • getP 方法计算并返回三次曲线上指定 t 处的一个点。

最初调用方法时,t 设置为 1.0(曲线结束)。 由于三次贝塞尔曲线不是线性的,我需要增量计算距点 pFrom 指定距离处的最近点。

在曲线上创建所有点的代码如下所示:

void CCubic::initEvenPointList(double distance, double offset)
{
    //TODO: Check if r can be 0 in the 1/r code, and how to handle/show it.
    lPoints.clear();
    minRadius = DBL_MAX;

    double t = 0;
    CCubicPoint ccP = getCubicPoint(t);
    lPoints.push_back(ccP);
    if(ccP.radius<minRadius) minRadius = ccP.radius;

    if(offset>0)
    {
        t = 1.0;
        getTAtDistance(getCubicPoint(0), offset, t);
        ccP = getCubicPoint(t);
        lPoints.push_back(ccP);
        if(ccP.radius<minRadius) minRadius = ccP.radius;
    }//if


    std::cout << "CCubic::initEventPointList -- Starting loop\n";
    while(t<1.0)
    {
        double newT = 1.0;
        getTAtDistance(ccP, distance, newT);
        if(newT>1) break;
        t = newT;
        ccP = getCubicPoint(t);
        lPoints.push_back(ccP);
        if(ccP.radius<minRadius) minRadius = ccP.radius;
    }

    ccP = getCubicPoint(1.0);
    lPoints.push_back(ccP);
    if(ccP.radius<minRadius) minRadius = ccP.radius;


    std::cout << "P(" << 0 << "): t = " << lPoints[0].t << "\n";
    double d = 0;
    for(int i=1; i<lPoints.size(); i++)
    {
        d+= (lPoints[i] - lPoints[i-1]).getLength();
        std::cout << "P(" << i - 1<< "): t = " << lPoints[i].t << ", d = " << d*400 << "\n";
    }//for
}

如果我在实际值中定义三次贝塞尔曲线:

  • A(-400, 0, 0)(起点)
  • B(0, -200, 0)(A 的控制点)
  • C(0, -200, 0)(D 的控制点)
  • D(400, 0, 0)(终点)

并将距离设置为 25。

我得到大约 34 分,两者之间的距离正确。一切正常。

然后我注意到,如果我将三次贝塞尔曲线定义为标准化并按比例放大(最大值为 1.0),即:

  • A(-1, 0, 0)(起点)
  • B(0, -0.5, 0)(A 的控制点)
  • C(0, -0.5, 0)(D 的控制点)
  • D(1, 0, 0)(终点)

然后我将距离设置为 25/400(比例为 400)。 如果计算整个曲线,放大后我只得到大约 4 个点。这不应该发生在数学中。所以应该有舍入错误,或者我的代码错误。

我给你getCubicPoint和getP的代码,还有DoubleCompare:

CPoint CCubic::getP(double f) const
{
    CPoint rP = pA*pow(1-f, 3) + pB*3*f*pow(1-f, 2) + pC*3*(1-f)*pow(f, 2) + pD*pow(f,3);

    return rP;
}

CCubicPoint CCubic::getCubicPoint(double f) const
{
    CPoint cP = pA*pow(1-f, 3) + pB*3*f*pow(1-f, 2) + pC*3*(1-f)*pow(f, 2) + pD*pow(f,3);
    CPoint pI = (pB - pA)*3 + (pA + pC - pB*2)*6*f + (pD + pB*3 - pC*3 - pA)*3*pow(f,2);
    CPoint pII = (pA + pC - pB*2)*6 + (pD + pB*3 - pC*3 - pA)*6*f;

    double r = (pI.x*pII.y - pII.x*pI.y) / pow((pow(pI.x,2) + pow(pI.y, 2)), 3.0/2.0);
    r = 1/r;
    if(r<0) r = -r;
    pII = pI.getNormal(true);       //Right normal
    pII = pII.getNormalized();

    return CCubicPoint(cP, pII, r, f);
}


int GLOBAL::DoubleCompare(double A, double B)
{
    if(abs(A - B) < std::numeric_limits<double>::epsilon()) return 0;
    if(A < B) return -1;
    return 1;
}

【问题讨论】:

    标签: c++ double precision


    【解决方案1】:

    double 具有 11 位指数和 53 位精度。这意味着 any 有限双精度具有相同的精度,无论其大小是 4、400 还是 4e300。归一化与“真实”范围与任何其他量级都无关紧要。

    需要注意的是,如果您使用的数字数量级相差很大。例如,在浮点路径中,1e300 + 1 == 1e300,因为没有足够的精度位来表示1

    我认为这种幅度差异导致了您的问题。 DoubleCompare内:

    if(abs(A - B) < std::numeric_limits<double>::epsilon()) return 0;
    

    epsilon 定义为在 1.0 时的最小可表示浮点差。我了解您的意图是允许浮点误差,但不同的幅度将需要不同的相对误差。 Bruce Dawson 的"Comparing Floating Point Numbers" 有更多细节和对其他技术的评论。

    (这里abs的使用也让我有点紧张,因为C语言中的abs只接受整数。因此,是否获得浮点绝对值取决于您包含的标题以及您之前是否在代码中使用过using namespace std;using std::abs;。也许我只是偏执,但我更喜欢C 的fabs 或明确的std::abs。)

    您的代码还不够完整,无法编译,所以我不确定,但我认为改进与DoubleCompare 的比较会给您带来一致的结果。

    【讨论】:

    • 谢谢,我错过了,晶圆厂!尽管我的迭代似乎太精确了,因为在某种程度上我没有达到 epsilon 以下。我必须将我的“自己的”epsilon 设置为 0.0001,否则我永远不会接近 0。而且我认为这是不切实际的,因为这会使我失去精度并“模糊”任何无限循环。此外,我不得不重写 getTAtDistance 中的代码,因为它根本无法以最佳方式工作,并且在无限循环中很容易陷入困境。但我注意到精度要好得多,但仍低于现实世界的值,这可能是由我的自定义 epsilon 引起的。
    猜你喜欢
    • 2016-12-19
    • 1970-01-01
    • 2017-01-23
    • 2012-06-24
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多