【问题标题】:detect the loss of precision in pure C++检测纯 C++ 中的精度损失
【发布时间】:2013-01-19 20:46:59
【问题描述】:

当您使用浮点数(floatdoublelong double 类型)进行运算时,是否可以检测到精度损失?说:

template< typename F >
F const sum(F const & a, F const & b)
{
    F const sum_(a + b);
    // The loss of precision must be detected (here or one line above) if some fraction bit is lost due to rounding
    return sum_;
}

x87 FPU 出现在目标架构上但没有asm 例程干预纯C++ 代码的情况下尤其感兴趣。 C++11gnu++11 特定功能也可以接受。

【问题讨论】:

  • 我认为数字的 C++11 类型特征确实有类似的东西
  • 也许numeric_limits&lt;T&gt; 类型特征的is_exact 值就是你要找的东西
  • 我认为这是不可能的,因为 x87 FPU 的状态字中的 P 位只有在二进制操作动作完成后才能检查已执行。
  • 检查sum-a==bsum-b==a?或者尝试使用 fenv.h(许多编译器没有正确实现)。
  • 特别是关于 x87,我相信您会在网络上(甚至在 SO)上找到很多很好的讨论。由于在不可预测的时间进行双重舍入,它使事情变得复杂。 gcc 有处理它的标志(其中一些只适用于 C)。大多数可移植性是将 sum 写入 volatile 变量并将其读回(强制舍入)。

标签: c++ gcc g++


【解决方案1】:

C++ 标准对浮点精度的概念非常模糊。没有完全符合标准的方法来检测精度损失。

GNU provides an extension 启用浮点异常。您想要捕获的异常是FE_INEXACT

【讨论】:

  • 与其说是 GNU 扩展,不如说是 GNU 试图符合多种标准。一是语言本身。另一个是 IEC 浮点标准,它规定了那些浮点异常。
  • @DavidHammen 好信息。有趣的是(与 C99 不同)C++ 标准也不承诺 IEC 浮点数。这是一个模糊的话题。
  • C99 通过__STDC_IEC_559__ 发布广告,C++98 通过std::numeric_limits&lt;...&gt;::is_iec559 发布广告。
【解决方案2】:

对您有帮助的是std::numeric_limits&lt;double&gt;::epsilon,它返回“1 与可表示的大于 1 的最小值之间的差”。换句话说,它告诉您最大的x>0,这样1+x 的计算结果为1。

【讨论】:

    【解决方案3】:

    您可以考虑在 boost 库中使用 interval arithmetic。可以保证计算过程中一个区间的误差一直在增加的性质:∀ x ∈[a,b], f(x) ∈ f([a,b])

    在您的情况下,您可以考虑将初始范围 [a-EPS,a+EPS] 用于初始编号 a。经过一系列操作后,得到的区间[x,y]abs(y-x) 将是您想知道的(最大)精度损失。

    【讨论】:

    • 我还没有真正尝试过,但应该有一个政策让你总是把它向外圆。它还提到了x87浮点单元,所以我认为它会对你有所帮助
    • Boost 为您处理舍入。请注意,您可以从 [a,a] 开始,并在间隔不再是一个点时检测精度损失(即进行一次向上舍入、向下舍入和比较的操作)。
    【解决方案4】:

    你可以使用类似下面的东西:

    #include <iostream>
    #include <fenv.h>
    
    #pragma STDC FENV_ACCESS ON
    
    template <typename F> F sum (const F a, const F b, F &error) {
        int round = fegetround();
    
        fesetround(FE_TONEAREST);
        F c = a + b;
    
        fesetround(FE_DOWNWARD);
        F c_lo = a + b;
    
        fesetround(FE_UPWARD);
        F c_hi = a + b;
    
        fesetround(FE_TONEAREST);
        error = std::max((c - c_lo), (c_hi - c));
    
        fesetround(round);
    
        return c;
    }
    
    
    int main() {
        float a = 23.23528;
        float b = 4.234;
        float e;
    
        std::cout << sum(a, b, e) << std::endl;
        std::cout << e << std::endl;
    }
    

    error 参数中返回对最大错误量的快速估计。请记住,切换舍入模式会刷新浮点单元 (FPU) 流水线,因此不要指望超快的速度。

    更好的解决方案是尝试区间算术(倾向于给出悲观的误差区间,因为没有考虑变量相关性)或仿射算术(跟踪变量相关性,因此给出更严格的误差范围)。

    在此处阅读这些方法的入门知识: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.36.8089

    【讨论】:

    • 在第一次调用fesetround之前使用fgetround保存FPU控制字更安全,然后在最后恢复它。
    • 如果您需要真正的一般精度,最好使用代数。
    • 答案已更新。你的意思是符号变量吗?是的,它们很棒——如果问题允许它们使用的话。
    • 我的意思是类似于doc.cgal.org/latest/Algebraic_kernel_d/index.html 实现。
    猜你喜欢
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 2020-07-08
    • 2011-02-01
    • 2015-04-26
    相关资源
    最近更新 更多