如何在 C# 中处理精度

本文关键字:处理 精度 | 更新日期: 2023-09-27 17:57:14

查找两点之间的积分点数的奇怪精确行为:

我正在为此编写一个算法,它是这样的(伪)。

给定 x1,y1 和 x2,y2

我计算了双倍 m,其中 m 是线段的梯度,给出为 (双精度)(y2-y1)/(x2-x1)然后我计算了双 c,其中 c 是 y 截距,给出为 y1 - (m*x1)

然后对于

i = 最小值(x1,x2) i <最大值(x1,x2)><最大值(y1,y2)>最后,返回结果 -1

该代码适用于某些测试用例,但在其他测试用例上失败,例如,当两个端点彼此垂直时,我不得不处理 m 的无穷大和 c 的 NaN 情况。但是,一个特殊的情况引起了我的注意,分别针对 x1,y1 和 x2、y2 的测试用例 43,38,17,6。

运行代码 j 从 6 开始,i 从 17 开始,所以这个点肯定在行段上,即使我不应该计算它,因为它是一个终点。奇怪的是,对于这个值 i, j != (m*i)+c = 5.9999999999...而不是 6。这怎么可能?我在哪里失去了精度?更重要的是,我是如何失去精度的?

法典:

        int cnt = 0;
        double i, j;
        double m = (double)(y2 - y1) / (x2 - x1);
        double c = y1 - (m * x1);
        for (i = Math.Min(x1, x2); i <= Math.Max(x1, x2); i++)
        {
            for (j = Math.Min(y1, y2); j <= Math.Max(y1, y2); j++)
            {
                if (j == (m * i) + c||double.IsInfinity(m) && double.IsNaN(c))
                    cnt++;
            }
        }
        return cnt - 2;

所以我将所有变量都更改为十进制,但不幸的是,我仍然得到失败的测试用例。但我想我已经在这里缩小了范围:d ecimal m = (十进制)(y2 - y1)/(x2 - x1);

如何在 C# 中处理精度

mcdouble s,所以(m*i)+c将返回一个double。 然而,j是一个int.所以你正在比较一个整数和一个双精度。给定浮点表示,在进行直接比较时,这将是一个问题。您需要将该比较的右侧转换为整数,或者进行某种非精确比较。或者,您可以使用不是浮点精度的东西,例如 decimal ,这不会显示此问题。

双打不能精确。它们只能精确到一定数量的数字。请记住,它们使用内部格式以字节为单位存储。这将不可避免地导致一些精度误差。

更糟糕的是,您放入双精度值中的某些值无法精确存储,即使没有进行计算。

让您意识到的示例:可以在此处测试将1.94分配给double变量,结果如下: 1.939999999999999946709294817992486059665679931640625

它的不良做法注定无法将两个浮点数与相等运算符进行比较。

有关浮点数的重要阅读:每个计算机科学家都应该知道的关于浮点运算的知识

将无限多个实数压缩成有限位数 需要近似表示。虽然有无限 许多整数,在大多数程序中整数计算的结果可以 以 32 位存储。相反,给定任何固定位数的位, 大多数使用实数的计算将产生 不能用那么多位精确表示。因此, 浮点计算的结果通常必须按顺序四舍五入 以适合其有限表示。此舍入误差为 浮点计算的特征。

作为解决方案,如果您真的想比较,您可以在比较之前用Math.round(x, decimals)敏感地对结果进行四舍五入。