首页 > 解决方案 > 如何提高计算浮点数的精度?

问题描述

我在 Microsoft Visual Studio Community 2019 中用 C++ 编写了一个代码片段,如下所示:

int m = 11;
int p = 3;
float step = 1.0 / (m - 2 * p);

可变步长是 0.200003,0.2 是我想要的。有什么提高精度的建议吗?

这个问题来自UNIFORM KNOT VECTOR。结向量是 NURBS 中的一个概念。你可以认为它只是一个这样的数字数组: U[] = {0, 0.2, 0.4, 0.6, 0.8, 1.0}; 两个相邻数字之间的跨度是一个常数。节点向量的大小可以根据某些条件改变,但范围在[0, 1]。

整个功能是:


typedef float NURBS_FLOAT;
void CreateKnotVector(int m, int p, bool clamped, NURBS_FLOAT* U)
{
    if (clamped)
    {
        for (int i = 0; i <= p; i++)
        {
            U[i] = 0;
        }

        NURBS_FLOAT step = 1.0 / (m - 2 * p);
        for (int i = p+1; i < m-p; i++)
        {
            U[i] = U[i - 1] + step;
        }

        for (int i = m-p; i <= m; i++)
        {
            U[i] = 1;
        }
    }
    else
    {
        U[0] = 0;
        NURBS_FLOAT step = 1.0 / m;
        for (int i = 1; i <= m; i++)
        {
            U[i] = U[i - 1] + step;
        }
    }
}

标签: c++

解决方案


让我们关注您的代码中发生的事情:

  1. 该表达式1.0 / (m - 2 * p)产生 0.2,最接近的可表示double值为 0.200000000000000011102230246251565404236316680908203125。请注意它的精确度——精确到 16 位有效十进制数字。这是因为,由于1.0double文字,分母被提升为double,并且整个计算以双精度完成,从而产生一个double值。

  2. 将上一步获得的值写入step,其类型为float。所以这个值必须四舍五入到最接近的可表示值,恰好是 0.20000000298023223876953125。

所以你引用的 0.200003 结果不是你应该得到的。相反,它应该更接近 0.200000003。

有什么提高精度的建议吗?

是的。将值存储在更高精度的变量中。例如,代替float step,使用double step。在这种情况下,您计算的值不会再次四舍五入,因此精度会更高。

你能得到精确的 0.2 值在随后的计算中使用它吗?不幸的是,对于二进制浮点算术,没有。在二进制中,数字 0.2 是一个周期分数:

0.2 10 = 0.0̅0̅1̅1̅ 2 = 0.0011 0011 0011... 2

请参阅浮点数学是否损坏?问题及其答案以获取更多详细信息。

如果你真的需要十进制计算,你应该使用库解决方案,例如Boost'scpp_dec_float。或者,如果您需要任意精度的计算,您可以使用例如cpp_bin_float来自同一个库的。请注意,这两种变体都将比使用内置 C++ 二进制浮点类型慢几个数量级。


推荐阅读