在C#中比较双精度浮点数的值

81

我有一个名为xdouble变量。 在代码中,x被赋值为0.1,然后我在一个“if”语句中将其与0.1进行比较。

if (x==0.1)
{
----
}

很不幸,它没有进入if语句。

  1. 我应该使用 Double 还是 double

  2. 这背后的原因是什么?你能提出一个解决方案吗?


2
请您添加双精度浮点数的声明,好吗? - Lars Corneliussen
一篇关于主题的全面阅读:计算机科学家应该了解的浮点算术知识 - Serge Wautier
https://dev59.com/UHRA5IYBdhLWcg3w_DLF - Mauricio Scheffer
18个回答

122

这是由于计算机存储浮点数值的方式导致的标准问题。在此搜索“浮点数问题”,可以找到大量信息。

简而言之,浮点数/双精度无法准确存储0.1。它总是会有一点偏差。

您可以尝试使用decimal类型来以十进制表示方式存储数字。因此,0.1将能够被精确地表示。


您想知道原因:

浮点数/双精度以二进制小数形式存储,而不是十进制小数形式。举个例子:

用十进制表示的12.34意味着

1 * 101 + 2 * 100 + 3 * 10-1 + 4 * 10-2

计算机以相同的方式存储浮点数,只是使用基数为210.01表示

1 * 21 + 0 * 20 + 0 * 2-1 + 1 * 2-2

现在,您可能知道有一些数字无法用我们的十进制表示法完全表示。例如,在十进制表示法中,1/30.3333333…。在二进制表示法中同样存在这种情况,只是无法表示的数字不同。其中之一是数字1/10。在二进制表示法中,其为0.000110011001100…

由于二进制表示法无法精确存储它,因此以四舍五入的方式进行存储。这就是您的问题所在。


20
我完全理解您上述的说法。但是,如果他写了 x = 0.01; 来给 x 赋值,然后将其与字面值 0.01 进行比较,那么假设没有使用计算来赋值 xx0.01 的实际底层二进制值不会相同吗?因此,(x == 0.01) 应该有效,不是吗?不过,一旦对 x 执行一些算术运算,所有结果都不确定了。另外,我想更明确地表达:应该写成 if (x == 0.01d),以明确指出 0.01 是一个 double(而不是从 float 转换为 double),这可能是问题所在。 - fourpastmidnight
@fourpastmidnight:我无法重现你所说的(.NET 4)。如果我将0.01与0.01进行比较,我会得到“true”。 - Tim Schmelter
4
是的,如果你执行x = 0.01; if ( x == 0.01),那么比较应该是相等的。当然,前提是没有隐式的浮点数转换(即使有可能仍然成立)。但是,原帖并没有说明第一个0.01是如何得到的。很可能它来自于某种计算。 - Vilx-
@Vilx - 确实,OP没有说明如何分配x值。如果该值是通过迭代计算进行分配的(即最终结果约为0.01),那么这可能是“预期”的。 :) - fourpastmidnight
对我来说,它返回true?为什么? - Imad
显示剩余3条评论

62

doubleDouble是相同的(doubleDouble的别名),可以互换使用。

用另一个值来比较double类型存在的问题在于,double只是近似值,而不是精确的值。所以当你把x设置为0.1时,实际上可能被存储为0.100000001之类的值。

与其检查相等性,不如检查差异是否小于定义的最小差异(公差)。例如:

if (Math.Abs(x - 0.1) < 0.0000001)
{
    ...
}

38
你需要将X-Y使用Math.Abs进行组合,并且使用一个value来进行比较。
你可以使用以下扩展方法的方式。
public static class DoubleExtensions
    {
        const double _3 = 0.001;
        const double _4 = 0.0001;
        const double _5 = 0.00001;
        const double _6 = 0.000001;
        const double _7 = 0.0000001;

        public static bool Equals3DigitPrecision(this double left, double right)
        {
            return Math.Abs(left - right) < _3;
        }

        public static bool Equals4DigitPrecision(this double left, double right)
        {
            return Math.Abs(left - right) < _4;
        }

        ...

由于你很少对双精度数调用方法,除了 ToString,我认为这是非常安全的扩展。

然后你可以像下面这样比较 xy

if(x.Equals4DigitPrecision(y))


这可能是Double结构的不错扩展! - Sergey Kucher

12

由于四舍五入的原因,不能总是精确地比较浮点数。为了进行比较

(x == .1)

电脑真正进行了比较

(x - .1) vs 0

由于浮点数在计算机中的表示方式,减法的结果并不总是能够被精确地表示。因此,你会得到一些非零值,条件表达式将评估为false

要解决这个问题,可以进行比较操作。

Math.Abs(x- .1) vs some very small threshold ( like 1E-9)

你能用一个例子清楚地说明吗?有什么解决方案,让我需要改变我的陈述吗? - stack_pointer is EXTINCT

6

根据文档

比较的精度 Equals方法应谨慎使用,因为两个看似相等的值由于两个值的精度不同而不相等。以下示例报告Double值.3333和除以3的1返回的Double不相等。

...

与其进行相等性比较,一种推荐的技术是定义两个值之间接受的差异范围(例如其中一个值的0.01%)。如果两个值之间的差的绝对值小于或等于该差异范围,则差异可能是由于精度不同而导致的,因此这些值可能相等。以下示例使用此技术来比较.33333和1/3,这两个Double值在前面的代码示例中被认为是不相等的。

因此,如果您确实需要一个double,则应使用文档中描述的技术。 如果可以,请将其更改为十进制数。 它会更慢,但您不会遇到此类问题。


5

请使用 decimal 数据类型,它不会出现这种“问题”。


9
使用十进制数进行操作会更慢。我们不应仅仅因为“它没有这个问题”就总是使用十进制类型。两种类型都有各自的用途。 - Evgeni Nabokov

4

官方微软帮助,特别关注“比较中的精度”部分,以回答问题。 https://learn.microsoft.com/en-us/dotnet/api/system.double.equals

// Initialize two doubles with apparently identical values
double double1 = .333333;
double double2 = (double) 1/3;
// Define the tolerance for variation in their values
double difference = Math.Abs(double1 * .00001);

// Compare the values
// The output to the console indicates that the two values are equal
if (Math.Abs(double1 - double2) <= difference)
   Console.WriteLine("double1 and double2 are equal.");
else
   Console.WriteLine("double1 and double2 are unequal.");

3

double(小写)只是System.Double的别名,因此它们完全相同。

原因请参见二进制浮点数和.NET。 简而言之:double不是一个精确的类型,"x"和"0.1"之间微小的差异会使其出错。


编辑队列已满,因此我会在此处发布新链接:https://csharpindepth.com/Articles/FloatingPoint - pogosama

3

由于浮点数的舍入和内部表示问题,精确比较浮点数值并不总是有效。

尝试使用不精确比较:

if (x >= 0.099 && x <= 0.101)
{
}

另一种选择是使用十进制数据类型。

2

双精度浮点数(在某些语言中称为float)由于舍入问题而存在问题,仅适用于需要近似值的情况。

十进制数据类型可以满足您的需求。

在.NET C#中,decimal和Decimal是相同的,double和Double类型也是相同的,它们都指向同一类型(decimal和double非常不同,正如您所见)。

请注意,Decimal数据类型有一些相关成本,因此如果您正在查看循环等内容,请谨慎使用。


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