在 C++ 中缩放 double 时的精度差异

Difference in precision when scaling double in C++

是否有必要缩放双精度值,或者在使用真实世界值时它们是否更精确?用 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
}

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

最初调用该方法时,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
}

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

并将距离设置为 25。

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

然后我注意到,如果我将三次贝塞尔曲线定义为归一化并将其放大(最大值为 1.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;
}

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

需要注意的是,如果您使用的数字大小相差很大。例如,在浮点路径中,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只取整数,所以能不能得到浮点数的绝对值取决于headers 你已经包括了什么,你之前是否在你的代码中做了 using namespace std;using std::abs;。也许我只是偏执,但我更喜欢 C 的 fabs 或明确的 std::abs.)

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