Mik*_*ley 73 language-agnostic floating-point comparison
我正在编写一些代码,其中包含以下内容:
double a = SomeCalculation1();
double b = SomeCalculation2();
if (a < b)
DoSomething2();
else if (a > b)
DoSomething3();
Run Code Online (Sandbox Code Playgroud)
然后在其他地方我可能需要做平等:
double a = SomeCalculation3();
double b = SomeCalculation4();
if (a == 0.0)
DoSomethingUseful(1 / a);
if (b == 0.0)
return 0; // or something else here
Run Code Online (Sandbox Code Playgroud)
简而言之,我有很多浮点数学正在进行,我需要对条件进行各种比较.我无法将其转换为整数数学,因为在这种情况下这样的事情毫无意义.
我以前读过浮点比较可能不可靠,因为你可以做这样的事情:
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
Run Code Online (Sandbox Code Playgroud)
简而言之,我想知道:我如何可靠地比较浮点数(小于,大于,相等)?
我使用的数字范围大致是从10E-14到10E6,所以我确实需要使用小数字和大数字.
我已将此标记为语言无关,因为无论我使用何种语言,我都对如何实现此目标感兴趣.
Mic*_*rdt 64
除非你在浮点/双精度限制的边缘工作,否则比较大/小并不是一个真正的问题.
对于"模糊等于"的比较,这个(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;
}
}
Run Code Online (Sandbox Code Playgroud)
它配备了一个测试套件.你应该立即解除任何没有的解决方案,因为它实际上保证在一些边缘情况下失败,比如有一个值0,两个非常小的值反对零或无穷大.
另一种选择(参见上面的链接以获取更多详细信息)是将浮点数的位模式转换为整数并接受固定整数范围内的所有内容.
在任何情况下,可能没有任何解决方案适合所有应用程序.理想情况下,您可以使用涵盖实际用例的测试套件来开发/调整自己的应用程序.
P-G*_*-Gn 14
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);
}
Run Code Online (Sandbox Code Playgroud)
比较浮点数时,有两种"模式".
第一个是在相对模式,其中的区别x和y相对认为它们的振幅|x| + |y|.在2D中绘图时,它给出以下轮廓,其中绿色表示相等x和y.(epsilon为了便于说明,我拿了0.5分).
相对模式是用于"正常"或"足够大"浮点值的模式.(稍后会详细介绍).
第二个是绝对模式,当我们简单地将它们的差异与固定数字进行比较时.它给出了以下轮廓(再次使用epsilon0.5和1 relth的1作为插图).
这种绝对比较模式用于"微小"浮点值.
现在的问题是,我们如何将这两种反应模式拼接在一起.
在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
Run Code Online (Sandbox Code Playgroud)
我们有x < y1 < y2,实际上y2 - x是比2000倍大y1 - x.然而,使用当前的解决方案,
nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
Run Code Online (Sandbox Code Playgroud)
相反,在上面提出的解决方案中,切换区域基于值|x| + |y|,其由下面的阴影方块表示.它确保两个区域连接正常.
此外,上面的代码没有分支,这可能更有效.认为如操作max和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 =~ aa =~ b暗示b =~ aa =~ 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(给定不精确的等式意味着在更高的不精确度下相等)建议的解决方案也验证了这些.
小智 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");
}
Run Code Online (Sandbox Code Playgroud)
晶圆厂 - 绝对价值 - 如果它们基本上是平等的,则需要处理.
小智 10
我们必须选择容差级别来比较浮点数.例如,
final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
Console.WriteLine("Oh yes!");
Run Code Online (Sandbox Code Playgroud)
一个说明.你的例子很有趣.
double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
Console.WriteLine("Oh no!");
Run Code Online (Sandbox Code Playgroud)
这里有些数学
a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.
1/3 != 1
Run Code Online (Sandbox Code Playgroud)
哦,是的..
你的意思是
if (b != 1)
Console.WriteLine("Oh no!")
Run Code Online (Sandbox Code Playgroud)