我使用tolerance=1e-10来检查两个3d向量是否相等,使用以下函数
is_same
:
bool is_same(const vector3d& x, const vector3d& y) {
var delta = x-y;
return delta.length() < tolerance; // tolerance is 1e-10
}
但是出现了一个我无法解决的情况。我有一个名为 lineSegments 的类,
struct lineSegments {
vector3d s, e; // s means start point, e means end point
}
当我计算两个线段的交集时。我用方程
(e1-s1)*k1+s1 = (e2-s2)*k2+s2
得到k1和k2,所以交点是(e1-s1)*k1+s1
。auto l1 = lineSegments{
{639482584.0, 2115435624.0, 0}, // s1
{1658825857.0, 1245760131.0, 0}}; // e1
auto l2 = lineSegments{
{1764535160.0, 1562640819.0, 0}, // s2
{1658825857.0, 1245760131.0, 0}}; // e2
我得到k1=1.0000000000000011,k2=0.99999999999999989。然后我用
is_same
检查与点P{1658825857.0, 1245760131.0, 0}
的交点,失败了。因为delta length=((e1-s1)*k1+s1-P).length()
是4.76837158203125E-07,比1e-10大。
如何修复这个错误?或者我应该使用小长度的向量来改变我的函数?
首先,正如其他评论提到的,比较浮点数是一个困难的话题,但经常需要。 一般来说,当您需要比较两个浮点数 a 和 b 时,您可以将它们的差异与某个常数进行比较:
abs(a - b)<some_constant
由于浮点数在内存中的表示方式,很难为 some_constant 选择一个固定值。如果您比较非常非常小的数字,则 some_constant 应该很小。但如果 a 和 b 非常大, some_constant 也应该相对于它们的大小增加。这样做的原因是,即使它们非常非常接近(但不完全相同),它们的差异也与数字具有相同的数量级,并且它将比我们最初选择的小常数大得多。 因此,在比较两个数字时,理想情况下您应该将一个常数乘以两个数字中最大的一个值:
abs(a - b)<some_constant*abs(max(a,b))
通过这种方式,您可以将常数缩放到数字的大小,这样它实际上才有意义。 我们可以为 some_constant 选择什么实际值?我不确定,我在这里看到了很多不同的解决方案。有时使用
std::numeric_limit<float>::epsilon()
,有时使用 ...::min()
。但它并不完美,因为 epsilon 是 1 和最接近的可表示浮点数之间的差,而 min 是最小可表示浮点数。因此,他们的情况非常特殊。通常我认为最好的解决方案是选择一个您认为与当前任务相关的常数。
这是关于比较两个数字,那么向量长度呢: 这里有两个问题。长度只是一个数字,那么你用什么来比较呢?你做不到
abs(a)<abs(a)*some_constant
,它达不到我们想要的效果。使用固定的预定义常数也不是最佳的。此外,长度是 sqrt(sqr(dx)+sqr(dy)+dqr(dz))
的结果,因此诸如浮点误差和大小差异之类的东西使得它的意义不大。相反,您可以做的是基于组件的比较:
abs(dx)<some_constant*mx && abs(dy)<some_constant*my && abs(dz)<some_constant*mz
其中
mx, my, mz
是每个维度的幅度乘数:mx=max(x1,x2)
等。
这有几个优点: 例如,您比较的向量在 x 中的大小可能与 y 中的大小相差很大,因此每个向量需要使用不同的常数。 其次,它使您免于昂贵的 sqrt() 操作。 希望这有帮助!