如何检查两个数字是否在浮点类型的精度限制的有效数字"x"范围内?

How can I check whether two numbers are within "x" significant figures of the precision limits of the floating point type?

本文关键字:精度 类型 有效数字 范围内 检查 何检查 两个 是否 数字      更新时间:2023-10-16

所以假设我们有一个浮点类型XType,其中有两个数字:

XType const a = 1.2345
XType const b = 1.2300

然后我想要一个函数IsClose(XType常量f1,XType常量f2,无符号常量truncated_files),这样

// the numbers are equal if the last two figures are ignored (1.23 == 1.23)
IsClose<XType>(a,b,2) == true
// the numbers are not equal if only the last is ignored (1.234 != 1.230)
IsClose<XType>(a,b,1) == false

到目前为止,我有一个丑陋的烂摊子,但我还没有说服自己这是正确的:

// check if two floating point numbers are close to within "figures_tolerance" figures of precision for the applicable type
template <typename FloatType>
bool const IsClose(FloatType const f1, FloatType const f2, unsigned const figures_tolerance)
{
  FloatType const tolerance_exponent = std::pow(10.0,figures_tolerance);
  FloatType const tolerance = 
    std::pow(tolerance_exponent,std::log10(f1)) * 
    std::numeric_limits<FloatType>::epsilon()
  ;
  return std::abs(f1 - f2) < tolerance;
}

我的推理是,公差应该是提升到数字超过或小于1.0的数量级的ε(ε所基于的有效数字)。这有道理吗?有没有更好、更可靠的方法?

编辑:我使用模板功能的解决方案如下(它基于下面用户763305的回答)

// check if two floating point numbers are within the last n digits of precision for the
// largest of the two numbers being compared.
template <typename FloatType>
bool const IsWithinPrecision(FloatType const f1, FloatType const f2, unsigned const n = 1U)
{
    FloatType const f_ref = std::max(std::abs(f1), std::abs(f2));
    FloatType const distance = std::abs(f1 - f2);
    FloatType const e = std::numeric_limits<FloatType>::epsilon();
    return distance < std::pow((FloatType) 10.0, (FloatType) n) * e * f_ref;
}

要测试两个数字是否在彼此的n有效位数内,请使用不等式

abs(a - b) < pow(0.1, n) * max(abs(a), abs(b))

然而,我通常发现测试有效位数是否至少是最大可能的有效位数(给定浮点类型的精度)减去n更有用。这可以使用不等式来完成

abs(a - b) < pow(10.0, n) * std::numeric_limits<...>::epsilon() * max(abs(a), abs(b))

换言之,n是我们通过舍入误差丢失的有效位数。类似n = 23的东西通常在实践中有效。

这样做的原因是浮点数aa以下和以上的下一个可表示浮点数之间的距离位于之间

0.5 * std::numeric_limits<...>::epsilon() * abs(a)

std::numeric_limits<...>::epsilon() * abs(a)

此外,如果你处理的是非常小的,或者更准确地说,非正规化的数字,那么上述不等式就不起作用。那么你应该使用不等式

abs(a - b) < pow(10.0, n) * max(
    std::numeric_limits<...>::epsilon() * max(abs(a), abs(b)),
    std::numeric_limits<...>::denorm_min()
)

由于这只是为了调试,因此可能会松懈,并使用简单的相对错误测试,例如:

if (fabs(f1 - f2) <= SomeNumber * fabs(f2)) ThingsAreGood else ThingsAreBad;

这假设f2是已知的良好(或至少已知更好)值,并且浮点运算中舍入的误差与f2成比例。请注意,计算可能会以复杂的方式产生错误。例如,如果在f1上加上和减去各种其他值,使得中间值具有比由f2表示的最终结果大得多的幅度,则舍入误差可能与那些大的中间值成比例,而不是与f2成比例。在这种情况下,您可能需要根据中间计算而不是f2来计算错误阈值。

考虑到Eric Postpischil指出的情况,该函数根据精度告诉两个数字是否足够接近。

bool const IsClose(FloatType const f1, FloatType const f2, unsigned const figures_tolerance)
{
    FloatType res = f1-f2;
    res = res*pow(10.0,figures_tolerance);
    return !bool(int(res));
}

编辑答案中使用的解决方案在我进行大量比较的情况下不起作用。

我写了一个函数,使用字符串与给定数字精度的进行比较

#include <iomanip>
/**
 * Compare two number with a given digit precision
 *
 * @tparam T - Number precision
 *
 * @param n1 - First number to compare
 * @param n2 - Second number to compare
 * @param n - The first n digits that must be equals between the two numbers
 *
 * @return True if the n first digits of the two numbers are equals, false otherwise
 */
template<typename T>
bool isEqual(T n1, T n2, int n)
{
    int                index = 0;
    std::ostringstream a, b;
    a         << std::setprecision(n);
    b         << std::setprecision(n);
    std::cout << std::setprecision(n);
    a         << std::fixed;
    b         << std::fixed;
    std::cout << std::fixed;
    a << n1;
    b << n2;
    while (a.str()[index] == b.str()[index] && index < n) {
        index++;
    }
    if (index != n) {
        std::cout << "n1 != n2nnn1 = " << a.str() << "nn2 = " << b.str() << "ndiffer at index " << index << std::endl;
    }
    return index == n;
}