这个话题已经提出了很多次在计算器上,但我相信这是一个新的起飞。 是的,我已经阅读布鲁斯道森的文章和什么每台计算机科学家应该知道关于浮点运算和这个漂亮的答案 。
据我了解,一个典型的系统上有平等比较浮点数时,四个基本问题:
- 浮点计算不准确
- 无论
ab
是“小”取决于规模a
和b
- 是否
ab
为“小”取决于类型a
和b
(例如浮动,双,长双) - 浮点通常具有+ -infinity,NaN和非规范化表示,其中任一种可以与幼稚制剂干扰
这个答案 -又名。 “谷歌的做法” - 似乎是受欢迎。 它处理所有的棘手案件。 而且它的规模比较非常精确,检查两个值是否固定数量的内ULPS对方。 因此,例如,一个非常大的数量的比较“几乎相等”无穷大。
然而:
- 这是非常混乱的,在我看来。
- 这不是特别是便携式,上内部表示严重依赖,采用了联合从浮法等读出的位
- 它只能处理单精度和双精度IEEE 754(尤其是没有长期的x86双)
我想类似的东西,但使用标准C ++和处理长一倍。 通过“标准”,我的意思是C ++ 03如果可能的话,如果需要C ++ 11。
这里是我的尝试。
#include <cmath>
#include <limits>
#include <algorithm>
namespace {
// Local version of frexp() that handles infinities specially.
template<typename T>
T my_frexp(const T num, int *exp)
{
typedef std::numeric_limits<T> limits;
// Treat +-infinity as +-(2^max_exponent).
if (std::abs(num) > limits::max())
{
*exp = limits::max_exponent + 1;
return std::copysign(0.5, num);
}
else return std::frexp(num, exp);
}
}
template<typename T>
bool almostEqual(const T a, const T b, const unsigned ulps=4)
{
// Handle NaN.
if (std::isnan(a) || std::isnan(b))
return false;
typedef std::numeric_limits<T> limits;
// Handle very small and exactly equal values.
if (std::abs(a-b) <= ulps * limits::denorm_min())
return true;
// frexp() does the wrong thing for zero. But if we get this far
// and either number is zero, then the other is too big, so just
// handle that now.
if (a == 0 || b == 0)
return false;
// Break the numbers into significand and exponent, sorting them by
// exponent.
int min_exp, max_exp;
T min_frac = my_frexp(a, &min_exp);
T max_frac = my_frexp(b, &max_exp);
if (min_exp > max_exp)
{
std::swap(min_frac, max_frac);
std::swap(min_exp, max_exp);
}
// Convert the smaller to the scale of the larger by adjusting its
// significand.
const T scaled_min_frac = std::ldexp(min_frac, min_exp-max_exp);
// Since the significands are now in the same scale, and the larger
// is in the range [0.5, 1), 1 ulp is just epsilon/2.
return std::abs(max_frac-scaled_min_frac) <= ulps * limits::epsilon() / 2;
}
我要求这个代码(一)处理所有的相关的情况下,(B)做同样的事情作为谷歌实施IEEE-754单精度和双精度,以及(c)是完全标准的C ++。
一个或多个这些索赔几乎可以肯定是错误的。 我会接受这样的证明,最好用固定任何回答。 一个好的答案应该包括一个或多个:
- 具体投入的不止不同
ulps
在最后的地方单位,但该函数返回true(较大的差别,越好) - 具体投入最多相差
ulps
在最后的地方单位,但该函数返回false(该差值越小越好) - (S)我已经错过任何情况下,
- 任何方式使这一代码依赖于不确定的行为,或者根据实现定义的行为中断。 (如果可能的话,请引用相关的规范。)
- 修正了什么问题(S)你识别
- 任何方式简化代码而不被破坏。
我想订一个不平凡的奖金在这个问题上。