我应该如何做浮点比较?

我正在写一些代码,我有一些东西:

double a = SomeCalculation1(); double b = SomeCalculation2(); if (a < b) DoSomething2(); else if (a > b) DoSomething3(); 

然后在其他地方,我可能需要做到平等:

 double a = SomeCalculation3(); double b = SomeCalculation4(); if (a == 0.0) DoSomethingUseful(1 / a); if (b == 0.0) return 0; // or something else here 

总之,我有很多浮点math,我需要做各种条件的比较。 我不能把它转换成整型math,因为在这种情况下这样的事情是毫无意义的。

我以前读过浮点比较可能是不可靠的,因为你可以有这样的事情:

 double a = 1.0 / 3.0; double b = a + a + a; if ((3 * a) != b) Console.WriteLine("Oh no!"); 

总之,我想知道:如何可靠地比较浮点数(小于,大于等于)?

我使用的数字范围大致从10E-14到10E6,所以我确实需要使用小数字和大数字。

我已经把这个标记为语言不可知论的,因为我感兴趣的是无论我使用什么语言,我都能做到这一点。

除非你在float / double精度限制的边缘工作,否则比较大/小是没有问题的。

对于“模糊等于”的比较,这个(Java代码,应该很容易适应)是经过大量工作并考虑到许多批评之后为浮点指南提出的:

 public static boolean nearlyEqual(float a, float b, float epsilon) { final float absA = Math.abs(a); final float absB = Math.abs(b); final float diff = Math.abs(a - b); if (a == b) { // shortcut, handles infinities return true; } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) { // a or b is zero or both are extremely close to it // relative error is less meaningful here return diff < (epsilon * Float.MIN_NORMAL); } else { // use relative error return diff / (absA + absB) < epsilon; } } 

它带有一个testing套件。 你应该马上解决没有的解决scheme,因为它实际上保证在某些边缘情况下失败,例如有一个值为0,两个非常小的值为零或无穷大。

另一种方法(更多细节见上面的链接)是将浮点数的位模式转换为整数,并接受固定整数范围内的所有内容。

无论如何,可能没有任何解决scheme对所有应用都是完美的。 理想情况下,您可以使用涵盖实际使用情况的testing套件来开发/调整自己的应用程序。

我有比较浮点数A < BA > B这是什么似乎工作的问题:

 if(A - B < Epsilon) && (fabs(AB) > Epsilon) { printf("A is less than B"); } if (A - B > Epsilon) && (fabs(AB) > Epsilon) { printf("A is greater than B"); } 

晶圆厂 – 绝对价值 – 照顾,如果他们本质上是平等的。

我们必须select一个容差水平来比较浮点数。 例如,

 final float TOLERANCE = 0.00001; if (Math.abs(f1 - f2) < TOLERANCE) Console.WriteLine("Oh yes!"); 

一个音符。 你的例子很有趣。

 double a = 1.0 / 3.0; double b = a + a + a; if (a != b) Console.WriteLine("Oh no!"); 

有些math在这里

 a = 1/3 b = 1/3 + 1/3 + 1/3 = 1. 1/3 != 1 

哦,是的

你的意思是

 if (b != 1) Console.WriteLine("Oh no!") 

我很快就有了浮点比较的想法

 infix operator ~= {} func ~= (a: Float, b: Float) -> Bool { return fabsf(a - b) < Float(FLT_EPSILON) } func ~= (a: CGFloat, b: CGFloat) -> Bool { return fabs(a - b) < CGFloat(FLT_EPSILON) } func ~= (a: Double, b: Double) -> Bool { return fabs(a - b) < Double(FLT_EPSILON) } 

TL; DR

使用下面的function,而不是迈克尔Borgwardt的,以避免在某些限制情况下的一些不良结果。

 bool nearlyEqual(float a, float b, float epsilon = 0.00001f, float relth = std::numeric_limits<float>::min()) { assert(0.f < epsilon); assert(epsilon < 1.f); if (a == b) return true; auto diff = std::abs(ab); auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max()); return diff < epsilon * std::max(relth, norm); } 

原始post

Michael Borgwardt的回答很好,但仍然有一个缺陷。 有可能find两个相同数量级的数字(a1, b1)(a2, b2) ,第二对中的成员之间的差异比第一个更大,然而,第二对中的数字是视为平等而不是第一。

例如,对于1e-5epsilon

 (a1, b1) = (1.175494865101066e-032, 1.175493836543509e-032) (a2, b2) = (1.1755049302714449e-032, 1.1754837713731301e-032) 

这两对显然差不多是相同的数量级 – 实际上,在意义上严格相等|a1+b1|=|a2+b2| 。 他们的分歧,

 |a1-b1| = 1.0285575569695016e-038 |a2-b2| = 2.1158898314801175e-037 

差别很大:在第二个和第一个之间超过了20倍。 但是,

 nearlyEqual(a1,b1,1e-5) = false nearlyEqual(a2,b2,1e-5) = true 

这种行为是由于function的不连续性。 由于diff漂浮在Float.MIN_NORMAL并且该函数可能只是通过对input的小扰动从一个分支切换到另一个分支,所以不能保证两个分支都是一致的并且将返回相同的结果。

目前这可能只发生在相对较小的数字上。 但是,如Float.MIN_NORMAL那样小的范围内进行相对于绝对差异testing的切换并不总是Float.MIN_NORMAL ,实际上人们可能希望将开关置于更高的值, 如此处所示 。 那么这种不连续可能会沦为“更普通”的对。

最简单的解决方法可能是replace分支testing以确保连续性,例如在post顶部提供的function。

新的relth参数控制着从相对比较到绝对比较的范围。

这个testing的解释是相当简单的。 在以原点为中心的方形|a| + |b| == relth |a| + |b| == relth |a| + |b| == relth ,默认的相对比较发生。 在这个平方中,用一个绝对的比较来代替,并且这个阈值被用来使得响应函数是连续的。

我不确定这个函数是否打破了目前被接受的答案所具有的属性。 当然这两种function是不同的,所以在某些情况下必然会提供不同的答案。 可以说最less的是这个函数通过了Michael Borgwardt提供的比较testing。

编辑

警告:math散乱在前面。

我现在认识到,函数的不同子域之间的边界附近的响应的不连续可能是可能改善的标志,但本身不是问题的根源。 另外, |a+b|=|c+d||ab| < |cd| |ab| < |cd| 应该暗示如果c=~d那么a=~b (我们称之为属性P)可能是有道理的,但似乎有点过于详尽。 也许这个属性可能来自更简单理想的属性,应该要求具有“近乎平等”的function。

以下属性=~应该是相当明显的:

  • 自我平等: a=~a
  • 对称: a=~b意味着b=~a
  • 反对的不变性: a=~b意味着(-a)=~(-b)

(我们没有a=~bb=~c意味着a=~c ,所以=~不是等价关系)。

我将添加以下更特定于浮点比较的属性;

  • 如果a<b<c ,则a=~c意味着a=~b (更近的值也应该相等)
  • 如果a,b,m>=0a=~b意味着(a+m)=~(b+m) (具有相同差值的较大值也应该相等)

请注意,属性P可以从第一个属性(伴随对称性和不变性)推导出来,所以上面给出的数值反例毕竟不是不足的。

这些特性已经对可能的近似平等function给予了强有力的限制。 我提议的functionvalidation它们。 也许一个或几个其他明显的属性丢失。 如果有人暗示不相等组的连通分量是凸的(不知何故,我觉得应该是),这将是伟大的。 另外,对于相反符号值的响应限制目前还不是很好。 我很乐意用最后一个replace

  • 如果b,m>=0a>=-ba=~b意味着(a+m)=~(b+m)

但是这并不真正感觉到直截了当。

例如,人们也可能希望具有ε-参数近似平等关系的家族的属性

  • 如果eps1 < eps2a =~_eps1 b意味着a =~_eps2 b

我build议的function也validation了这一点。

标准的build议是使用一些小的“epsilon”值(可能取决于您的应用程序select),并考虑彼此的epsilon内浮动相等。 例如类似的东西

 #define EPSILON 0.00000001 if ((a - b) < EPSILON && (b - a) < EPSILON) { printf("a and b are about equal\n"); } 

更完整的答案是复杂的,因为浮点错误是非常微妙和令人困惑的理由。 如果你确实关心平等,那么你可能正在寻求一个不涉及浮点的解决scheme。

从Michael Borgwardt&bosonix的回答中适应PHP:

 class Comparison { const MIN_NORMAL = 1.17549435E-38; //from Java Specs // from http://floating-point-gui.de/errors/comparison/ public function nearlyEqual($a, $b, $epsilon = 0.000001) { $absA = abs($a); $absB = abs($b); $diff = abs($a - $b); if ($a == $b) { return true; } else { if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) { return $diff < ($epsilon * self::MIN_NORMAL); } else { return $diff / ($absA + $absB) < $epsilon; } } } } 

最近我一直在类似的问题上磕磕绊绊,一直在做一些testing。

在某些情况下,如果两个浮点数具有相同的string值, 与浮点数(i..e, (float)$float1 === (float)$float2)相比,它们相对相同,无论它们是如何派生的。 然而,在其他情况下,即使两个浮标具有相同的string值,但如果以不同的方式导出浮标,它们有时也会返回相对不同的值。

请看下面的例子:

 $float1 = 0.04 + 0.02; $float2 = 0.04 + 0.01 + 0.01; $float3 = 0.03 + 0.03; echo 'Values:'; var_dump($float1); echo '<br>'; var_dump($float2); echo '<br>'; var_dump($float3); echo '<br><br>'; echo 'Comparisons:'; var_dump($float1 - $float2); echo '<br>'; var_dump($float2 - $float3); echo '<br>'; var_dump($float1 - $float3); echo '<br>'; 

在PHP 5.3上运行,结果如下:

价值观:

浮动(0.06)

浮动(0.06)

浮动(0.06)

比较:

浮动(-6.93889390391E-18)

浮动(6.93889390391E-18)

浮动(0)

正如你所看到的,当浮点数比较时,$ float2和$ float1和$ float3是不一样的。 它们之间的唯一区别是它们是如何派生的。 你会认为这是有道理的,假设浮动是如何派生的并不重要,只是它的最终价值是什么,但从上面的例子可以看出,这是一个糟糕的假设。

实际的区别是非常小的,为了计算起来并不重要,但是比较它们是否重要。

如果你想自信地把浮游物比作浮游物,在做比较之前,我build议你:

 $float1 = 0.04 + 0.02; $float2 = 0.04 + 0.01 + 0.01; $float3 = 0.03 + 0.03; //Cast to string, then back to float $float1 = (float)(string)$float1; $float2 = (float)(string)$float2; $float3 = (float)(string)$float3; echo 'Values:'; var_dump($float1); echo '<br>'; var_dump($float2); echo '<br>'; var_dump($float3); echo '<br><br>'; echo 'Comparisons:'; var_dump($float1 - $float2); echo '<br>'; var_dump($float2 - $float3); echo '<br>'; var_dump($float1 - $float3); echo '<br>'; 

价值观:

浮动(0.06)

浮动(0.06)

浮动(0.06)

比较:

浮动(0)

浮动(0)

浮动(0)

我认为这个问题来自于浮点数组成部分以不同精度四舍五入到计算机上以二进制forms存储的方式。 如果你转换成一个string,然后又返回到一个浮点数,则舍入是在最终值上完成的,所以如果最后的值具有相同的string值,它们的浮点值也总是相同的。

希望有所帮助。

编辑:

我刚刚在php.net上遇到了BC(二进制计算)math函数。 这些看起来像上面一样,然而他们返回string值,所以如果你想浮游物,确保你投回浮游物之后。 这里是文档: http : //php.net/manual/en/ref.bc.php

见下文:

 echo '$float1 - $float2 = '; var_dump(bcsub($float1,$float2,2)); echo '<br>'; echo '$float2 - $float3 = '; var_dump(bcsub($float2,$float3,2)); echo '<br>'; echo '$float1 - $float3 = '; var_dump(bcsub($float1,$float3,2)); echo '<br>'; 

返回:

$ float1 – $ float2 = string(4)“0.00”

$ float2 – $ float3 = string(4)“0.00”

$ float1 – $ float3 = string(4)“0.00”

比较平等/不平等的双打的最好方法就是把差异的绝对值与足够小(取决于你的上下文)的值进行比较。

 double eps = 0.000000001; //for instance double a = someCalc1(); double b = someCalc2(); double diff = Math.abs(a - b); if (diff < eps) { //equal } 

我试着用上面的注释来写一个平等函数。 以下是我想到的:

编辑:从Math.Max(a,b)更改为Math.Max(Math.Abs​​(a),Math.Abs​​(b))

 static bool fpEqual(double a, double b) { double diff = Math.Abs(a - b); double epsilon = Math.Max(Math.Abs(a), Math.Abs(b)) * Double.Epsilon; return (diff < epsilon); } 

思考? 我仍然需要制定一个大于,小于。

你需要考虑到截断错误是一个相对的错误。 两个数字大致相等,如果他们的差异大约是他们的ulp(单位在最后一个地方)。

但是,如果你进行浮点运算,你的错误潜力会随着每个操作而增加(特别是小心!),所以你的错误容忍度需要相应的增加。

如果有人想看看为什么比较浮动是不好的这是今天我打的PHP的一个例子。 PHP使得这个问题非常混乱,因为它打印出与string相同的值。 您需要高精度地打印以查看出了什么问题。

即使我们处于无限循环,以下脚本最终也会失败。 你可以看到,对于两个string的一些用法,它们看起来好像是相同的值,它们是不一样的, $secstypes是一个浮点数(我知道它打印为双精度)。

 <?php while(1) { $time = time(); $millis = $time * 1000; $hours = $millis / 3600000; $mins = $hours * 60; //$secs = (int)($mins * 60); $secs = ($mins * 60); printf("gettype(time) == %s\n", gettype($time)); printf("gettype(secs) == %s\n", gettype($secs)); printf("$time == $secs\n", $time, $secs); printf("%d == %d\n", $time, $secs ); printf("%f == %f\n", $time, $secs ); printf("%.1f == %.1f\n", $time, $secs ); printf("%.10f == %.10f\n", $time, $secs ); printf("%.10F == %.10F\n", $time, $secs ); printf("%b == %b\n", $time, $secs ); assert($secs == $time); if($secs != $time) { break; } }