我正在使用三角形进行计算。但是我需要知道三个给定的点是否不在同一条线上。为此,我正在计算三角形的面积
area =(Ax *(By-Cy)+ Bx *(Cy-Ay)+ Cx *(Ay-By));
如果面积等于零,则所有三个点共线。 但是问题是,由于double和float都非常不准确,所以它永远不会真正等于零,所以
if(area==0){
printf("It's not a triangle");
}
不起作用。如何解决这个问题的正确方法?
答案 0 :(得分:1)
让我们清除一些因果关系,并进行更深入的研究。
面积公式错误
面积是OP公式的1/2,但与0.0相比并没有什么不同。
// area=(Ax* (By-Cy) + Bx* (Cy-Ay) + Cx* (Ay-By));
area=(Ax* (By-Cy) + Bx* (Cy-Ay) + Cx* (Ay-By))/2;
不准确
“因为双精度和浮点数非常不准确”本身就是不准确的。像整数一样,所有有限FP值都是 exact 。在将他们的运算与数学除法进行比较时,他们误称“不准确”。像整数除法,FP除法和其他基本FP数学OP一样,它们的定义与数学运算不同。 7/3和7.0 / 3.0都不会得出数学2 1 / 3 ,而是一个不同的值。当C使用IEEE数学模型时,该“商”不是近似的,而是精确的。
比较多少?
“比较2个双打”误导了代码需要执行的6 double
的复杂比较。
查看测试公式
只要子步骤不舍入,具有Ax* (By-Cy) + Bx* (Cy-Ay) + Cx* (Ay-By)
操作数的 double
将不舍入。在 general 中,这是不可能的。解决方法是
使用long double
执行测试。这不能消除问题,只会使其变小/可能性较小。注意long double
不需要更高的精度。
天真的方法采用结果|计算面积|并与 epsilon 进行比较。低于该绝对值的区域被视为“零”。这不能很好地扩展,因为 epsilon 确实取决于操作数相对于该区域的大小。需要相对ε。建议fmax(|ax|,|bx|,|cx|) * fmax(|ay|,|by|,|cy|) * DBL_EPSILON
。这只是一阶近似值。
区域公式是一个 signed 区域。有效地反转a,b,c的顺序会使该区域的符号反转。如果operand_new=operand*(1 +/- DBL_EPSILON)
对8个操作数中的任何一个进行小的扰动导致区域符号改变,则可以将该区域评估为“足够接近零”。
相减会破坏精度。将x
与y
交换可能有助于减少内部项。重新订购3种产品的减法会有所帮助。
更好的重新排序可以采用形成6种产品的形式:Ax By,-Ax Cy,Bx Cy,-Bx Ay,Cx Ay,-Cx By,然后将它们求和。
使用Kahan summation algorithm SO,也许利用fma()
都可以使这两种方法受益。
对我来说,我将探索#4b或#3。如果OP发布了Minimal, Complete, and Verifiable example,示例数据和预期的示例结果,则可以使用真实的代码。缺少这一点,请考虑一下有关模糊问题的入门思路。
答案 1 :(得分:-1)
您发现:舍入误差有多少?如果使用双精度,并且单次运算的结果为x,则舍入误差最高可达abs(x)/ 2 ^ 52。 (如果不使用double,则使用double。)
您执行此操作,然后在By-Cy,Cy-Ay,Ay-By中找到舍入误差。这三个误差乘以Ax,Bx和Cx。三种产品都有自己的舍入误差。然后添加前两个产品时出错,然后添加第三个产品。您将所有这些错误加起来并获得最大总错误e。
因此,如果面积小于e,则可以假定它们在一条直线上。
对此进行改进:如果Ax,Bx,Cx均为正(例如100、101、102.5),则计算平均值,然后从Ax,Bx和Cx中减去。这使您的数字更小,舍入误差也更小。
答案 2 :(得分:-2)
我会尝试这样的事情:
#include <float.h>
...
if ( (area < FLT_EPSILON) && (area > -FLT_EPSILON) )
{
printf("It's not a triangle");
}