在 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
}
此方法计算三次曲线上的一个点与三次曲线上另一点的距离。
- pFrom 是计算距离的点。
- t 将被增量计算并定义新的
增量完成时曲线上指定距离处的点。
- 方法 getP 计算并 returns 三次曲线上指定 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;
}
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
的比较会给您带来一致的结果。
是否有必要缩放双精度值,或者在使用真实世界值时它们是否更精确?用 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 计算并 returns 三次曲线上指定 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;
}
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
的比较会给您带来一致的结果。