计算浮点数的差

浮点数不是完美精确的表示,尤其是在涉及到舍入误差和不同数值规模时。

一般实现:

fabs(a - b) < epsilon

这种通过比较差的绝对值来判断浮点数是否相等的方法,存在一些潜在的问题:

  1. 误差累积:在连续的浮点数运算中,误差可能会逐步累积。这会导致最后的结果偏离真实值,这样用差值来比较就可能得到错误的结果。
  2. 选择合适的epsilon不好确定:epsilon 的选择可能依赖于具体的应用和上下文,并不能确定一个通用的值。太大或者太小的 epsilon 都可能造成不准确的判断。例如,太小的值可能会使接近但并非相等的数被误判为相等;而太大的数则可能让实际相等的数被误判为不等。
  3. 容易受到溢出和欠流的影响:如果两个浮点数的差值太大或者太小,可能会导致溢出或者欠流,从而无法准确的表示出实际的差值。
  4. 在一些需要极高精度的场合这种方式可能无法满足需求。

以下是一些改进浮点数比较策略的方法:

相对误差比较

使用相对误差而非绝对误差来比较浮点数。这涉及到计算两个数的差相对于数值大小的比例,以确定它们是否足够接近。

bool areAlmostEqual(double a, double b, double epsilon) {
    // 在非常小的值附近使用绝对误差比较
    if (fabs(a - b) < epsilon) {
        return true;
    }
    // 使用相对误差比较
    return fabs(a - b) < epsilon * fmax(fabs(a), fabs(b));
}

使用 ULPs 比较(单位最后位置)

ULPs 比较是一种浮点数比较策略,它通过比较两个浮点数之间的表示差异来确定它们是否足够接近。这种方法可以处理不同数值规模的准确性问题,但实现起来较为复杂。

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <limits>
#include <type_traits>
 
template <class T>
std::enable_if_t<not std::numeric_limits<T>::is_integer, bool>
equal_within_ulps(T x, T y, std::size_t n)
{
    // Since `epsilon()` is the gap size (ULP, unit in the last place)
    // of floating-point numbers in interval [1, 2), we can scale it to
    // the gap size in interval [2^e, 2^{e+1}), where `e` is the exponent
    // of `x` and `y`.
 
    // If `x` and `y` have different gap sizes (which means they have
    // different exponents), we take the smaller one. Taking the bigger
    // one is also reasonable, I guess.
    const T m = std::min(std::fabs(x), std::fabs(y));
 
    // Subnormal numbers have fixed exponent, which is `min_exponent - 1`.
    const int exp = m < std::numeric_limits<T>::min()
                  ? std::numeric_limits<T>::min_exponent - 1
                  : std::ilogb(m);
 
    // We consider `x` and `y` equal if the difference between them is
    // within `n` ULPs.
    return std::fabs(x - y) <= n * std::ldexp(std::numeric_limits<T>::epsilon(), exp);
}
 
int main()
{
    double x = 0.3;
    double y = 0.1 + 0.2;
    std::cout << std::hexfloat;
    std::cout << "x = " << x << '\n';
    std::cout << "y = " << y << '\n';
    std::cout << (x == y ? "x == y" : "x != y") << '\n';
    for (std::size_t n = 0; n <= 10; ++n)
        if (equal_within_ulps(x, y, n))
        {
            std::cout << "x equals y within " << n << " ulps" << '\n';
            break;
        }
}

建议:

  1. 优先使用数学库函数:一些数学库提供了特制的函数用于浮点数的比较,例如 C++ 的 std::numeric_limits<double>::epsilon() 可以提供 double 类型可接受的最小差值。如果可行,优先使用这些函数,因为它们考虑了浮点数的底层细节。
  2. 避免差值比较:在设计算法和编写代码时,尽量避免将两个浮点数相减然后比较差值。这种情况下,尽可能设计算法以避免这种直接比较。
  3. 自定义比较运算符:在面向对象语言中(如 C++),你可以为使用到的浮点数类型定义自己的比较运算符,这些运算符内部使用更稳健的浮点数比较策略。
  4. 减少浮点数的使用:如转为int。
posted @   天下太平  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示