用于比较PHP中浮点相等性的公式

时间:2016-09-09 12:30:55

标签: php floating-point

如何比较浮点数has been answered here的问题。这个问题的不同之处在于我在问这些公式。两个最高投票答案的解决方案略有不同:

if (abs(($a-$b)/$b) < $epsilon) { … }

if (abs($a-$b) < $epsilon) { … }

为什么第一个答案包含分区?它不会导致不准确的结果吗?例如(使用简单数字),让$ a和$ b都等于0.01,并假设$ a - $ b得到0.0001,$ epsilon为0.001。

((((0.01 - 0.01) == 0.0001) / 0.01 == 0.01) < 0.001) : false

,而

(((0.01 - 0.01) == 0.0001) < 0.001) : true

我的数学可能有点生疏,但我错过了什么?

我应该何时使用一个公式而不是另一个?

3 个答案:

答案 0 :(得分:3)

这可能允许用相对误差而不是绝对误差来检查epsilon。

比较这两种情况:

function areEqual(float $a, float $b) : bool {
    return abs(($a - $b) / $b) < 0.00001;
}
areEqual(10000, 10000.01);
areEqual(0.0000001, 0);
  

关于上述示例值的事实:为方便起见,我们这里的epsilon是0.00001 - 最小的epsilon可能比这些值小得多,所以让我们忽略这个事实。我们的算法假定$a$b都相似,因此我们是否除以$a$b并不重要。实际上,10000应该比那个(一个非常大的指数)大得多,0.0000001可以小得多,但为了方便起见,我们假设这些值可能会导致问题。< / p>

现在你已经可以看到差异了。

对于大数字:如果比较的浮点数非常大,则epsilon可能太小。浮点数内部只能存储一定数量的精度数字,而指数可能大于数字。结果,浮点错误的来源,即浮点数的最终数字,将出现在可能高于单位数的某处。换句话说,对于极大的浮点数,绝对误差可能大于1,更不用说0.00001的epsilon。

对于小数字:这更为明显。这两个数字都已经小于epsilon。即使你将它们与0进行比较,虽然相对误差是无限大的,但你仍然认为它们是相等的。对于这种情况,您要么将两个操作数相乘,要么减少epsilon。它们实际上是相同的,但就实现而言,将差异除以其中一个操作数更为方便,这些操作数将乘以小数(/ 0.0001相当于* 10000)或除以向下搜索大数字(/ 10000,而差异有望小于10000

此检查还有另一个名称。虽然abs($a - $b)被称为绝对误差,但我们通常使用相对误差,即绝对误差÷近似值。由于值也可能为负值,因此我们abs代替整个事件($a - $b) / $b。在这种情况下,我们的“epsilon”,0.00001意味着我们的容忍相对误差为0.00001,即0.001%错误。

<小时/> 请记住,并非绝对安全。在程序中进行多次转换之后,例如,您可以使用一些大数字来添加/乘以数字,然后再次减去数字,将大数字中的不纯错误留给人类仍然可以忽略不计,但值得注意你的epsilon值。因此,在选择epsilon值或浮点比较算法之前,请务必三思。

作为最佳做法,请避免使用较小的数字添加,减去或乘以大数字。他们会增加错误的机会。在开发(特别是简化)算法时,始终要考虑到它们可能是浮点数中的错误。这可能会将工作量增加到一个愚蠢的程度,但只要你意识到这一点,这种担心有时可以避免你被踢出团队。

答案 1 :(得分:0)

这是准确性与精确度的问题。如果你不进行除法,那么你正在考虑精度,所以你知道你的标准,并且对于小数点后第五位(或者你选择的任何东西)都是好的。如果你进行分工,你说的是准确性(想想百分比误差),所以如果你卖的是金耳环,那么减掉一盎司是很糟糕的,如果你正在衡量自己,那就很糟糕了。

答案 2 :(得分:0)

除此之外还考虑到浮点数具有指数的事实。这意味着当数字本身变大时,两个连续数字之间的最小差异会变大。

例如,1e-3002e-300是两个不同的数字(对于IEE 754 64位类型),但1e3001e300 + 1e-300是相同的。