如何正确比较2个双打?

时间:2018-10-31 22:11:41

标签: c double comparison

我正在使用三角形进行计算。但是我需要知道三个给定的点是否不在同一条线上。为此,我正在计算三角形的面积

  

area =(Ax *(By-Cy)+ Bx *(Cy-Ay)+ Cx *(Ay-By));

如果面积等于零,则所有三个点共线。 但是问题是,由于double和float都非常不准确,所以它永远不会真正等于零,所以

if(area==0){
 printf("It's not a triangle");   
 }

不起作用。如何解决这个问题的正确方法?

3 个答案:

答案 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 中,这是不可能的。解决方法是

  1. 使用更高的精度

使用long double执行测试。这不能消除问题,只会使其变小/可能性较小。注意long double不需要更高的精度。

  1. 使用某种 epsilon

天真的方法采用结果|计算面积|并与 epsilon 进行比较。低于该绝对值的区域被视为“零”。这不能很好地扩展,因为 epsilon 确实取决于操作数相对于该区域的大小。需要相对ε。建议fmax(|ax|,|bx|,|cx|) * fmax(|ay|,|by|,|cy|) * DBL_EPSILON。这只是一阶近似值。

  1. 寻找标志变更

区域公式是一个 signed 区域。有效地反转a,b,c的顺序会使该区域的符号反转。如果operand_new=operand*(1 +/- DBL_EPSILON)对8个操作数中的任何一个进行小的扰动导致区域符号改变,则可以将该区域评估为“足够接近零”。

  1. 重新排序公式。

相减会破坏精度。将xy交换可能有助于减少内部项。重新订购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"); }