为什么这个简单的双断言在C#中失败

时间:2013-08-18 15:31:46

标签: c# floating-point floating-point-precision

以下测试将在C#

中失败
Assert.AreEqual<double>(10.0d, 16.1d - 6.1d);

问题似乎是浮点错误。

16.1d - 6.1d == 10.000000000000002

这使我在编写使用double的代码的单元测试时感到头痛。有办法解决这个问题吗?

4 个答案:

答案 0 :(得分:5)

十进制系统和双精度的二进制表示之间没有确切的转换(请参阅下面@PatriciaShanahan关于原因的优秀评论)。

在这种情况下,数字的.1部分是问题,它不能用双精度有限地表示(如1/3不能有限地精确表示为十进制数)。

用于解释所发生情况的代码段:

double larger = 16.1d; //Assign closest double representation of 16.1.
double smaller = 6.1; //Assign closest double representation of 6.1.
double diff = larger - smaller; //Assign closest diff between larger and  
                                //smaller, but since a smaller value has a  
                                //larger precision the result will have better  
                                //precision than larger but worse than smaller. 
                                //The difference shows up as the ...000002.

在比较双打时,始终使用Assert.Equal overload delta参数。

或者,如果您确实需要精确的十进制转换,请使用decimal数据类型,该数据类型具有另一个二进制表示形式,并且在您的示例中将返回10

答案 1 :(得分:2)

浮点数是基于指数的实际值的估计值,因此测试失败正确。如果您需要两个十进制数的精确等价,则可能需要检查十进制数据类型。

答案 2 :(得分:1)

如果您使用的是NUnit,请使用Within选项。您可以在此处找到其他信息:http://www.nunit.org/index.php?p=equalConstraint&r=2.6.2

答案 3 :(得分:0)

我赞同安德斯阿贝尔。使用浮点数表示将无法实现此目的。
IEE 1985-754的直接结果中只有可以表示的数字
Powers of 2 in the sum
可以存储并精确计算(只要选择的位数允许)。

例如:
1024 * 1.75 * 183.375 / 1040.0675&lt; - 将被精确地存储 10 / 1.1&lt; - 不会精确地存储

如果您对有理数的精确表示不感兴趣,可以使用分数编写自己的数字实现。
这可以通过保存分子,分母和符号来完成。然后需要执行乘法,减法等操作(很难确保良好的性能)。 toString() - 方法看起来像这样(我假设cachedRepresentation,cachedDotIndex和cachedNumerator是成员变量)

 public String getString(int digits) {
            if(this.cachedRepresentation == ""){
                this.cachedRepresentation += this.positiveSign ? "" : "-";  
                this.cachedRepresentation += this.numerator/this.denominator; 
                this.cachedNumerator = 10 * (this.numerator % this.denominator);
                this.cachedDotIndex = this.cachedRepresentation.Length;
                this.cachedRepresentation += ".";
            }

            if ((this.cachedDotIndex + digits) < this.cachedRepresentation.Length)
                return this.cachedRepresentation.Substring(0, this.cachedDotIndex + digits + 1);

            while((this.cachedDotIndex + digits) >= this.cachedRepresentation.Length){
                this.cachedRepresentation += this.cachedNumerator / this.denominator;
                this.cachedNumerator = 10 * (this.cachedNumerator % denominator);
            }
            return cachedRepresentation;
        }

这对我有用。在操作本身有很长的数字我遇到了一些太小的数据类型的问题(通常我不使用c#)。我认为对于经验丰富的c#-developer来说,实现这一点应该没有问题,没有小数据类型的问题。

如果你想实现这个,你应该在初始化时和使用euclids最大公共分割器的操作之前缩小分数。

非有理数可以(在每种情况下我都知道)由算法指定,该算法尽可能接近精确表示(并且计算机允许)。