我正在编写一些代码,其中包含以下内容:
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
简而言之,我有很多浮点运算,我需要对条件进行各种比较。我无法将其转换为整数数学,因为在这种情况下这样的事情毫无意义。
我之前读过浮点比较可能不可靠,因为你可以做这样的事情:
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
简而言之,我想知道:我如何可靠地比较浮点数(小于,大于,相等)?
我使用的数字范围大致是从10E-14到10E6,所以我确实需要使用小数字和大数字。
我已将此标记为语言无关,因为无论我使用何种语言,我都对如何实现此目标感兴趣。
答案 0 :(得分:62)
比较大/小并不是一个问题,除非你在浮点/双精度限制的边缘工作。
对于“模糊等于”比较,这个(Java代码,应该很容易适应)是我在做了大量工作并考虑到许多批评之后为The Floating-Point Guide提出的:
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;
}
}
它附带一个测试套件。你应该立即驳回任何没有的解决方案,因为在某些边缘情况下它几乎可以保证失败,例如有一个值为0,两个非常小的值与零相反,或无穷大。
另一种选择(参见上面的链接以获取更多详细信息)是将浮点数的位模式转换为整数并接受固定整数范围内的所有内容。
在任何情况下,可能没有任何解决方案适用于所有应用程序。理想情况下,您可以使用涵盖实际用例的测试套件开发/调整自己的应用程序。
答案 1 :(得分:13)
我遇到了比较浮点数A < B
和A > B
的问题
这似乎有用:
if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is less than B");
}
if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is greater than B");
}
晶圆厂 - 绝对价值 - 如果它们基本相同则负责。
答案 2 :(得分:11)
我们必须选择容差级别来比较浮点数。例如,
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!");
这里有些数学
a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.
1/3 != 1
哦,是的..
你的意思是
if (b != 1)
Console.WriteLine("Oh no!")
答案 3 :(得分:10)
bool nearly_equal(
float a, float b,
float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
// those defaults are arbitrary and could be removed
{
assert(std::numeric_limits<float>::epsilon() <= epsilon);
assert(epsilon < 1.f);
if (a == b) return true;
auto diff = std::abs(a-b);
auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
return diff < std::max(relth, epsilon * norm);
}
比较浮点数时,有两种&#34;模式&#34;。
第一个是相对模式,其中x
和y
之间的差异被认为与其幅度|x| + |y|
相对。在2D中绘制时,它给出以下轮廓,其中绿色表示x
和y
的相等。 (为了便于说明,我拿了一个0.5的epsilon
。
相对模式是用于&#34;正常&#34;或者&#34;足够大&#34;浮点值。 (稍后会详细介绍)。
第二个是绝对模式,当我们简单地将它们的差异与固定数字进行比较时。它提供了以下配置文件(同样,epsilon
为0.5,relth
为1,用于说明。
这种绝对的比较模式是用于&#34;微小的&#34;浮点值。
现在问题是,我们如何将这两种反应模式拼接在一起。
在Michael Borgwardt的回答中,切换基于diff
的值,该值应低于relth
(答案中为Float.MIN_NORMAL
)。此开关区域在下图中以阴影线显示。
因为relth * epsilon
小于relth
,绿色补丁不会粘在一起,这反过来又给解决方案带来了不好的属性:我们可以找到x < y_1 < y_2
和x == y2
的数字三元组但是x != y1
但x = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
。
举一个惊人的例子:
x < y1 < y2
我们有y2 - x
,实际上y1 - x
比nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
大2000多倍。然而,使用当前的解决方案,
|x| + |y|
相比之下,在上面提出的解决方案中,切换区域基于max
的值,其由下面的阴影方块表示。它确保两个区域连接得很好。
此外,上面的代码没有分支,这可能更有效。请考虑{<1}}和abs
等先验需要分支的操作,通常会有专门的汇编指令。出于这个原因,我认为这种方法优于另一种解决方案,即通过将nearlyEqual
切换为diff < relth
来修复迈克尔的diff < eps * relth
,这样就可以产生相同的反应模式。
这些模式之间的切换是围绕relth
进行的,在接受的答案中将其视为FLT_MIN
。这个选择意味着float32
的表示限制了浮点数的精度。
这并不总是有意义的。例如,如果您比较的数字是减法的结果,那么FLT_EPSILON
范围内的某些内容可能更有意义。如果它们是减去数字的平方根,则数值不精确可能更高。
当您考虑将浮点与0
进行比较时,这是显而易见的。在这里,任何相对比较都将失败,因为|x - 0| / (|x| + 0) = 1
。因此,当x
处于计算不精确的顺序时,比较需要切换到绝对模式 - 并且很少低于FLT_MIN
。
这就是上面引入relth
参数的原因。
此外,如果不将relth
与epsilon
相乘,则此参数的解释很简单,并且对应于我们对这些数字所期望的数值精度级别。
(此处主要是为了我自己的乐趣)
更一般地说,我假设一个行为良好的浮点比较运算符=~
应该具有一些基本属性。
以下是相当明显的:
a =~ a
a =~ b
隐含b =~ a
a =~ b
暗示-a =~ -b
(我们没有a =~ b
而b =~ c
暗示a =~ c
,=~
不是等价关系。
我会添加以下属性,这些属性更适用于浮点比较
a < b < c
,则a =~ c
隐含a =~ b
(更接近的值也应该相等)a, b, m >= 0
,则a =~ b
隐含a + m =~ b + m
(具有相同差异的较大值也应相等)0 <= λ < 1
,那么a =~ b
暗示λa =~ λb
(可能不那么明显参与)。这些属性已经对可能的近似相等函数提出了强有力的约束。上面提出的功能验证了它们。也许缺少一个或几个明显的属性。
当人们认为=~
是由=~[Ɛ,t]
和Ɛ
参数化的平等关系系列relth
时,还可以添加
Ɛ1 < Ɛ2
则a =~[Ɛ1,t] b
隐含a =~[Ɛ2,t] b
(给定容差的等式意味着在更高的容忍度下相等)t1 < t2
则a =~[Ɛ,t1] b
暗示a =~[Ɛ,t2] b
(给定不精确的等式意味着在更高的不精确度下相等)建议的解决方案也验证了这些。
答案 4 :(得分:3)
我想在swift中进行浮点比较
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)
}
答案 5 :(得分:1)
Michael Borgwardt对PHP的改编&amp; bosonix的回答:
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;
}
}
}
}
答案 6 :(得分:0)
标准建议是使用一些小的“epsilon”值(可能根据您的应用程序选择),并考虑彼此的epsilon内的浮点数相等。例如
之类的东西#define EPSILON 0.00000001
if ((a - b) < EPSILON && (b - a) < EPSILON) {
printf("a and b are about equal\n");
}
更完整的答案很复杂,因为浮点错误极其微妙且令人困惑。如果您真正关心任何精确意义上的平等,那么您可能正在寻求一种不涉及浮点的解决方案。
答案 7 :(得分:0)
我尝试用上面的注释编写一个相等的函数。这就是我想出的:
编辑:从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);
}
思考?我仍然需要研究一个大于,小于一个。
答案 8 :(得分:0)
您应该问自己为什么要比较这些数字。如果您知道比较的目的,那么您还应该知道所需的数字准确性。在每种情况和每种应用程序上下文中这都是不同的。但是在几乎所有实际情况下,都必须具有 绝对 精度。很少会应用相对精度。
举个例子:如果您的目标是在屏幕上绘制图形,那么您可能希望浮点值在映射到屏幕上的相同像素时比较相等。如果屏幕尺寸为1000像素,并且数字在1e6范围内,则您可能希望100与200进行比较。
给出所需的绝对精度,则算法变为:
public static ComparisonResult compare(float a, float b, float accuracy)
{
if (isnan(a) || isnan(b)) // if NaN needs to be supported
return UNORDERED;
if (a == b) // short-cut and takes care of infinities
return EQUAL;
if (abs(a-b) < accuracy) // comparison wrt. the accuracy
return EQUAL;
if (a < b) // larger / smaller
return SMALLER;
else
return LARGER;
}
答案 9 :(得分:0)
我想出了一个简单的方法来将 epsilon 的大小调整为要比较的数字的大小。所以,而不是使用:
iif(abs(a - b) < 1e-6, "equal", "not")
如果 a
和 b
可以很大,我将其更改为:
iif(abs(a - b) < (10 ^ -abs(7 - log(a))), "equal", "not")
我想这并不能满足其他答案中讨论的所有理论问题,但它的优点是作为一行代码,因此可以在 Excel 公式或 Access 查询中使用它,而无需 VBA 函数.
我搜索了一下其他人是否使用过这种方法,但我没有找到任何东西。我在我的应用程序中对其进行了测试,它似乎运行良好。因此,它似乎是一种适用于不需要其他答案复杂性的上下文的方法。但我想知道它是否有我没有想到的问题,因为似乎没有其他人在使用它。
如果有原因,日志测试对各种大小的数字的简单比较无效,请在评论中说明原因。
答案 10 :(得分:-1)
比较平等/不平等的双精度的最佳方法是获取差异的绝对值,并将其与足够小(取决于您的上下文)值进行比较。
double eps = 0.000000001; //for instance
double a = someCalc1();
double b = someCalc2();
double diff = Math.abs(a - b);
if (diff < eps) {
//equal
}
答案 11 :(得分:-1)
您需要考虑截断错误是相对错误。两个数字大致相等,如果它们的差异与它们的ulp(最后一个单位)一样大。
但是,如果你进行浮点计算,你的错误可能会随着每次操作而增加(尤其是减法!),因此你的容错需要相应增加。