简体   繁体   English

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

[英]How can I check whether two numbers are within “x” significant figures of the precision limits of the floating point type?

So suppose we have a float type XType in which we have two numbers:因此,假设我们有一个浮点型 XType,其中有两个数字:

XType const a = 1.2345
XType const b = 1.2300

Then I want a function IsClose(XType const f1,XType const f2,unsigned const truncated_figures) such that然后我想要一个函数 IsClose(XType const f1,XType const f2,unsigned const truncated_figures) 这样

// 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

So far I have this ugly mess, but I'm yet to convince myself it's correct:到目前为止,我有这个丑陋的烂摊子,但我还没有说服自己这是正确的:

// 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;
}

My reasoning is that the tolerance should be the epsilon raised to the order of magnitude that the number exceeds or subseeds 1.0 (the significant figures for which the epsilon is based).我的推理是,容差应该是将 epsilon 提高到数量超过或低于 1.0(epsilon 所基于的有效数字)的数量级。 Does this make sense?这有意义吗? Is there a better, more reliable way?有没有更好、更可靠的方法?

EDIT: My solution using the template function is below (it is based on user763305's answer below)编辑:我使用模板功能的解决方案如下(它基于下面 user763305 的回答)

// 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;
}

To test whether two numbers are within n significant digits of each other, use the inequality要测试两个数字是否在彼此的n有效数字内,请使用不等式

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

However, I usually find it more useful to test if the number of significant digits is at least the maximum possible number of significant digits (given the precision of the floating point type) minus n .但是,我通常发现测试有效数字的数量是否至少是有效数字的最大可能数量(考虑到浮点类型的精度)减去n更有用。 This can be done using the inequality这可以使用不等式来完成

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

In other words, n is the number of significant digits we have lost through rounding errors.换句话说, n是我们舍入错误而丢失的有效数字的数量。 Something like n = 2 or 3 usually works in practice.n = 23这样的东西通常在实践中起作用。

The reason this works is that the distances between a floating point number a and the next representable floating point numbers below and above a lie between之所以这样的作品是一个浮点数之间的距离a和下一个可表示浮动低于和高于点数a间谎言

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

and

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

Also, the above inequality does not work if you are dealing with very small, or more precisely, denormal numbers.此外,如果您正在处理非常小的或更准确的非正规数,则上述不等式不起作用。 Then you should instead use the inequality那么你应该使用不等式

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

Since this is just for debugging, it may be possible to be lax and use a simple test for relative error, such as:由于这只是为了调试,可能会松懈,使用简单的相对误差测试,例如:

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

This supposes that f2 is the known good (or at least known-to-be-better) value and that the error from rounding in floating-point operations is proportional to f2 .这假设f2是已知好的(或至少已知更好的)值,并且浮点运算中舍入的误差与f2成正比。 Note that computations can produce errors in complicated ways.请注意,计算可能会以复杂的方式产生错误。 For example, if various other values were added to and subtracted from f1 along the way, so that intermediate values had much larger magnitudes than the final result represented by f2 , then the rounding errors may be proportional to those large intermediate values rather than to f2 .例如,如果在此过程中将各种其他值添加到f1或从f1减去,因此中间值的幅度比f2表示的最终结果大得多,则舍入误差可能与那些较大的中间值成正比,而不是与f2成正比. In this case, you may need to compute an error threshold based on the intermediate calculations rather than on f2 .在这种情况下,您可能需要根据中间计算而不是f2来计算错误阈值。

Taking care of the situation pointed out by Eric Postpischil as well, This function tells whether the 2 numbers are close enough or not according to the precision.考虑到 Eric Postpischil 指出的情况,这个函数根据精度告诉 2 个数字是否足够接近。

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));
}

The solution used in the edit answer did not work in my case for large number comparison.编辑答案中使用的解决方案在我的情况下不适用于大量比较。

I wrote a function using string comparison with a given digit precision我使用给定数字精度的字符串比较编写了一个函数

#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 != n2\n\nn1 = " << a.str() << "\nn2 = " << b.str() << "\ndiffer at index " << index << std::endl;
    }

    return index == n;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM