比较a_float == b_float
之类的两个浮点数正在寻找麻烦,因为a_float / 3.0 * 3.0
由于舍入错误而可能不等于a_float
。
通常做的事情就像fabs(a_float - b_float) < tol
。
如何计算tol
?
理想情况下,公差应该大于一个或两个最不重要数字的值。因此,如果单精度浮点数使用tol = 10E-6
应该是正确的。但是,对于a_float
可能非常小或可能非常大的一般情况,这不适用。
如何针对所有一般情况正确计算tol
?我特别感兴趣的是C或C ++案例。
答案 0 :(得分:16)
此博客文章包含一个示例,相当简单的实现,以及它背后的详细理论 http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 它也是一个系列之一,所以你可以随时阅读更多。 简而言之:对于大多数数字使用ULP,对于接近零的数字使用epsilon,但仍有一些警告。如果你想确定你的浮点数学,我建议你阅读整个系列。
答案 1 :(得分:9)
据我所知,一个人没有。
没有一般的“正确答案”,因为它可能取决于应用程序对精度的要求。
例如,在屏幕像素中工作的2D物理模拟可能会决定1/4像素足够好,而用于设计核电站内部的3D CAD系统可能不会。
我看不到从外面以编程方式决定这种情况的方法。
答案 2 :(得分:5)
C头文件<float.h>
为您提供常量FLT_EPSILON
和DBL_EPSILON
,这是1.0和float / double可以表示的大于1.0的最小数字之间的差值。您可以根据数字的大小和您希望容忍的舍入误差来缩放:
#include <float.h>
#ifndef DBL_TRUE_MIN
/* DBL_TRUE_MIN is a common non-standard extension for the minimum denorm value
* DBL_MIN is the minimum non-denorm value -- use that if TRUE_MIN is not defined */
#define DBL_TRUE_MIN DBL_MIN
#endif
/* return the difference between |x| and the next larger representable double */
double dbl_epsilon(double x) {
int exp;
if (frexp(x, &exp) == 0.0)
return DBL_TRUE_MIN;
return ldexp(DBL_EPSILON, exp-1);
}
答案 3 :(得分:3)
欢迎来到陷阱,陷阱和漏洞的世界。如其他地方所述,浮点相等和容差的通用解决方案确实存在不。鉴于此,程序员可以在特定情况下使用工具和公理。
fabs(a_float - b_float) < tol
有一个缺点OP提到:“对于a_float可能非常小或可能非常大的一般情况,它不能很好地工作。” fabs(a_float - ref_float) <= fabs(ref_float * tol)
更好地应对变体范围。
OP的“单精度浮点数使用tol = 10E-6”对于C和C ++来说有点令人担忧,因此很容易将float
算术推广到double
,然后它就是double
的“容差” {1}},而不是float
,发挥作用。考虑float f = 1.0; printf("%.20f\n", f/7.0);
许多新程序员没有意识到7.0
导致double
精度计算。建议您使用double
代码,除非大量数据需要float
更小的尺寸。
C99提供nextafter()
,可用于帮助衡量“容忍度”。使用它,可以确定下一个可表示的数字。这将有助于OP“...存储类型的完整有效位数减去1 ...以允许舍入错误。” if ((nextafter(x, -INF) <= y && (y <= nextafter(x, +INF))) ...
使用的{em>种 tol
或“容忍”通常是问题的症结所在。大多数情况下(恕我直言)相对容差很重要。即G。 “x和y是否在0.0001%之内”?有时需要绝对容差。例如“x和y是否在0.0001”之内?
容差的值通常是有争议的,因为最佳值通常取决于情况。 0.01以内的比较可能适用于美元的财务申请,但不适用于日元。 (提示:请务必使用允许轻松更新的编码样式。)
答案 4 :(得分:1)
舍入误差根据操作使用的值而有所不同。
您可以使用epsilon因子代替固定容差,而不是:
bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;
return min_a <= b && max_a >= b;
}
答案 5 :(得分:0)
虽然公差的值取决于具体情况,但如果您正在寻找精确比较,则可以使用机器epsilon值,numeric_limits :: epsilon()(库限制)作为公差。该函数返回1和大于1的最小值之间的差值,该值可表示数据类型。 http://msdn.microsoft.com/en-us/library/6x7575x3.aspx
如果您要比较浮点数或双精度数,则epsilon的值会有所不同。例如,在我的计算机中,如果比较浮点数,则epsilon的值为1.1920929e-007,如果比较两倍,则epsilon的值为2.2204460492503131e-016。
对于x和y之间的相对比较,将epsilon乘以x和y的最大绝对值。
上面的结果可以乘以ulps(最后一个位置的单位),这可以让你玩精度。
#include <iostream>
#include <cmath>
#include <limits>
template<class T> bool are_almost_equal(T x, T y, int ulp)
{
if( std::abs(x-y) <= std::numeric_limits<T>::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp ){
return true;
}else{
return false;
}
}
答案 6 :(得分:-3)
当我需要比较浮点数时,我使用这样的代码
bool same( double a, double b, double error ) {
double x;
if( a == 0 ) {
x = b;
} else if( b == 0 ) {
x = a;
} else {
x = (a-b) / a;
}
return fabs(x) < error;
}