简体   繁体   English

如何在C#中处理精度

[英]How to deal with precision in C#

Bizarre precision behavior in finding the number of integral points between two points exclusive: 查找两点之间的积分点数的奇异精度行为不包括:

I was writing an algorithm for this which went like this (pseudo). 我正在为此编写一个算法(伪)。

Given x1,y1 and x2, y2 给定x1,y1和x2,y2

I calculated double m where m is the gradient of the line segment given as (double)(y2-y1)/(x2-x1) then I calculated double c where c is the y intercept given as y1 - (m*x1) 我计算了double m,其中m是以(double)(y2-y1)/(x2-x1)给出的线段的斜率,然后我计算了double c,其中c是以y1-(m * x1)给出的y截距

then for i = Min(x1,x2) i < Max(x1,x2) for j = Min(y1, y2) j < Max(y1,y2) if j = (m*i) + c then ++ 然后对于i = Min(x1,x2)i <Max(x1,x2)对于j = Min(y1,y2)j <Max(y1,y2)如果j =(m * i)+ c,则++

finally, return the result -1 最后,返回结果-1

The code works for some test cases but fails on others for instance when the two endpoints are perpendicular to each other I had to deal with infinity for m, and NaN cases for c. 该代码适用于某些测试用例,但在其他一些用例上却失败,例如,当两个端点彼此垂直时,我必须处理m的无穷大,而处理c的NaN情况。 But, one particular case caught my eye, Test case 43,38,17,6 for x1,y1 and x2, y2 respectively. 但是,一个特殊的案例引起了我的注意,分别针对x1,y1和x2,y2的测试案例43,38,17,6。

running the code j starts at 6 and i at 17 so this point is definitely on the line segment even though I shouldn't be counting it because it is an end-point. 运行代码j从6开始,而i从17开始,所以这一点肯定在线段上,即使我不应该将其计算在内,因为这是一个终点。 What's bizarre is for this value i, j != (m*i)+c = 5.9999999999... instead of 6. how is that possible? 这个值i,j!=(m * i)+ c = 5.9999999999 ...而不是6,这是什么奇怪?这怎么可能? where am I losing precision for this? 我在哪里为此失去精度? More importantly how i'm I losing precision? 更重要的是,我如何失去精度?

code: 码:

        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;

So I changed all my variables to decimal but unfortunately, I still get failed test cases. 所以我将所有变量都更改为十进制,但是不幸的是,我仍然无法通过测试。 But I think i've narrowed it down to this point here :decimal m = (decimal)(y2 - y1) / (x2 - x1); 但我认为我已经将其缩小到这一点:小数m =(小数)(y2-y1)/(x2-x1);

m and c are double s, so (m*i)+c is going to return a double . mcdouble ,因此(m*i)+c将返回double j however, is an int . 但是j是一个int So you're comparing an integer to a double. 因此,您正在将整数与双精度进行比较。 Given floating point representation , this is going to be an issue SOMEWHERE when doing direct comparisons. 给定浮点表示形式 ,在进行直接比较时,这将成为一个问题。 You need to either cast the right-hand side of that comparison as an integer, or do some sort of non-exact comparison. 您需要将比较的右侧强制转换为整数,或者进行某种非精确比较。 Alternatively you could use something that is not floating point precision, like decimal , which won't show this issue. 或者,您可以使用非浮点精度的内容,例如decimal ,这不会显示此问题。

Doubles cannot be precise. 双打不能精确。 They are only precise up to a certain number of digits. 它们仅精确到一定位数。 Remember they use an internal format to be stored in bytes. 请记住,它们使用内部格式以字节存储。 This will inevitable cause some precision error. 这将不可避免地引起一些精度误差。

And even worse, some values you put into a double are unable to be precisely stored, even with no calculation made. 甚至更糟的是,即使不进行计算,也无法精确存储您放入双精度值的某些值。

Example to make you aware: Assigning 1.94 to a double variable can be tested here and will result in: 1.939999999999999946709294817992486059665679931640625 ! 使您知道的示例:可以在此处测试将1.94分配给double变量,结果将为: 1.939999999999999946709294817992486059665679931640625

Its bad practice and doomed to fail to compare two floating point numbers with equality operator. 这是错误的做法,并且注定无法将两个浮点数与相等运算符进行比较。

Important read about floating point numbers: What Every Computer Scientist Should Know About Floating-Point Arithmetic 有关浮点数的重要阅读: 每位计算机科学家都应了解的浮点算法

Squeezing infinitely many real numbers into a finite number of bits requires an approximate representation. 将无限多个实数压缩为有限数量的位需要近似表示。 Although there are infinitely many integers, in most programs the result of integer computations can be stored in 32 bits. 尽管有无限多个整数,但是在大多数程序中,整数计算的结果可以存储在32位中。 In contrast, given any fixed number of bits, most calculations with real numbers will produce quantities that cannot be exactly represented using that many bits. 相反,在给定固定位数的情况下, 大多数使用实数的计算将产生无法使用那么多位数精确表示的数量。 Therefore the result of a floating-point calculation must often be rounded in order to fit back into its finite representation. 因此,浮点计算的结果通常必须四舍五入,以重新适合其有限表示形式。 This rounding error is the characteristic feature of floating-point computation. 舍入误差是浮点计算的特征。

As solution if you really want to compare you can sensitively round the results with Math.round(x, decimals) before comparing them. 作为解决方案,如果您真的要比较,可以在比较结果之前用Math.round(x, decimals)敏感地舍入结果。

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

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