C#中使用double进行除法运算时出现精度丢失问题

8

我知道这个问题已经被反复讨论了,但是我似乎无法得到一个简单的双精度浮点数一步除法的预期结果,所以我想知道是否有编译器标志或其他奇怪的东西我没有考虑。考虑以下示例:

double v1 = 0.7;
double v2 = 0.025;
double result = v1 / v2;

当我在最后一行之后断点并在VS调试器中检查时,“result”的值为27.999999999999996。 我知道我可以通过改用“decimal”来解决它,但在周围的程序中不可能这样做。难道两个低精度的双精度数不能除以正确的值28吗?唯一的解决方案真的是使用Math.Round来四舍五入结果吗?


请查看“相关”侧边栏中的任何项目,例如此链接:https://dev59.com/S1LTa4cB1Zd3GeqPXRpf。 - Hans Kesting
1
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html 值得一读。 - Habib
6个回答

20

两个低精度的双精度浮点数不能准确地相除得到28,这难道不奇怪吗?

其实并不奇怪。0.7和0.025都无法在double类型中准确表示。涉及的确切值为:

0.6999999999999999555910790149937383830547332763671875
0.025000000000000001387778780781445675529539585113525390625

现在你是否感到惊讶,因为除法并没有给出完全是28的结果?垃圾进,垃圾出...

正如你所说,表示十进制数的正确结果是使用decimal。如果你的程序中其余部分使用了错误的类型,这意味着你需要权衡:得到错误答案的代价和改变整个程序的代价哪个更高。


好的,你从哪里得到了精确的值?我尝试过但是无法解决! - yamen
你是怎么知道“精确”值的? - CyprUS
2
@yamen @CyprUS:我有一个叫做DoubleConverter的小类,它查看位模式以构建十进制表示。请参见http://csharpindepth.com/Articles/General/FloatingPoint.aspx - Jon Skeet
1
@JonSkeet 你能大概估计一下,你在 Stack Overflow 上的声望有多少是通过回答这一个问题及其变体获得的吗?请用双精度浮点数回答。 - yamen
很棒的答案 - 谢谢你深入探究。我想对我来说是 Math.Round!:P - J23
我喜欢你的成本注释。@Metal450 - Math.Round在大多数情况下可能有效,但是在某些情况下可能会出现舍入误差。如果你在处理金钱方面,当你开始丢失一分钱时,人们往往会感到非常不满意。 - Paddy

6

当涉及到floatdouble时,精度总是一个问题。

这是计算机科学中已知的问题,每种编程语言都受到影响。为了最小化这些与舍入相关的错误,一个完整的数值分析领域专门研究此类问题。

例如,让我们来看下面的代码:

```java public static void main(String[] args) { float a = 0.1f; float b = 0.2f; System.out.println(a + b == 0.3f); } ```

你会期望输出结果为true,但实际上你会得到false

        float v = .001f;            
        float sum = 0;
        for (int i = 0; i < 1000; i++ )
        {
            sum += v;
        }

4

与“double”数字的“简单性”或“小型化”无关。严格来说,无论是0.7还是0.025,在计算机内存中可能都不能完全存储为这些数字,因此在它们上面进行计算可能会得到有趣的结果,前提是你需要高精度。

所以,是的,请使用“十进制”或四舍五入。


4

用类比的方式解释:

想象一下你正在使用基数3。在基数3中,0.1是(按照十进制) 1/3或0.333333333'。

因此,在基数3中,您可以精确地表示1/3(十进制),但尝试用十进制表达它时会产生舍入误差。

同样,对于某些十进制数字,您也可以得到完全相同的情况:它们可以在十进制中被精确地表示,但在二进制中无法精确地表示;因此,您会遇到舍入误差。


2

对于你的第一个问题,简短回答是:不奇怪。浮点数是实数的离散近似,这意味着在进行算术运算时舍入误差会传播和放大。

有一个称为数值分析的数学领域,主要处理如何在使用这种近似值时最小化误差。


2
这是通常的浮点数不精确性问题。并非每个数字都可以表示为double类型,这些小的不准确性会累加起来。这也是您不应将double与精确数字进行比较的原因之一。我刚刚测试了一下,result.ToString() 显示为 28(可能是在double.ToString()中发生了某种四舍五入?)。但是 result == 28 返回了 false。而(int)result 返回了 27。所以您需要预料到这样的不准确性。

是的,我也尝试了ToString() - 但显然它必须做一些内部舍入,正如你也发现的那样... - J23

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接