为什么double类型的变量会产生意外结果?

时间:2011-03-01 07:16:03

标签: .net variables floating-point floating-accuracy

我的理智检查失败,因为双变量不包含预期的结果,这真的很奇怪。

double a = 1117.54 + 8561.64 + 13197.37;
double b = 22876.55;
Console.WriteLine("{0} == {1}: {2}", a, b, a == b);

给我们这个输出:

22876.55 == 22876.55: False

进一步检查显示变量a实际上包含值22876.550000000003。

这在vb.net中也是可重现的。我 ?发生了什么事?

6 个答案:

答案 0 :(得分:3)

浮点类型并不总是能够准确地表示其精确的十进制值。根据您的观点,它可能是“已知错误”或“按设计”。无论哪种方式,它都是浮点类型的内部表示的结果,也是错误的常见来源。

这个问题几乎是不可避免的,缺少编写代表符号值的复杂计算机代数系统,而不是数字类型。打开Windows计算器,确定4的平方根,然后从该值中减去2。您将获得一些非常接近0的无意义浮点数,但不是完全 0.平方根计算的结果未存储为完全 2,所以当你从中减去2时,你会得到一个“意外”的结果。出乎意料的是,除非你知道关于base 2算法的肮脏小秘密。

如果您感到好奇,可能会有几个地方可以找到有关原因的更多信息。 Jon Skeet编写了an article来解释.NET Framework上下文中的二进制浮点运算。如果你有时间,你还应该仔细阅读恰当命名的出版物What Every Computer Scientist Should Know About Floating-Point Arithmetic

但最重要的是,您不应期望能够将浮点运算的结果与浮点文字进行比较。在这种特定情况下,您可以尝试使用decimal类型。它不是一个真正的“解决方案”(参见其他答案,那些可怕的数学概念,如epsilons),但结果通常更具可预测性,因为decimal类型更能准确地表示基数为10的数字(例如那些用于货币和财务计算)。

答案 1 :(得分:1)

这是浮点舍入,这是设计的 - 你不应该期望浮点变量精确地等于任何其他浮点变量,除了一组非常罕见的特殊情况。

另见this question

答案 2 :(得分:1)

正如锐齿所说,浮点数变量是如何保存在内存中的。您可以阅读更多here

另外,要检查两个浮动数字是否相等,您可以使用以下内容:

double a = 1117.54 + 8561.64 + 13197.37;
double b = 22876.55;
Console.WriteLine("{0} == {1}: {2}", a, b, fabs(a-b) < 1e-9);

这是根据这些数字的不同来检查的。如果他们之间的差异只是在第9位之后,你可以认为他们是平等的。 为了获得更高的精度,只需使用较低的epsilon(两个数字之间的最大差异被视为相等)。

答案 3 :(得分:1)

你是理智的。您只是处理无法以任意数量的二进制数字完美存储的数字。您将以任何语言看到这一点 - 舍入“错误”是浮点格式固有的,因此也是硬件中的。

如果你真的需要完美的比较,尝试使用十进制技巧:选择你将要处理的最小部分,并用它来表达一切。如果你愿意,你自己的个人普朗克是不变的。例如,您的示例代码将变为:

int a = 111754 + 856164 + 1319737;
int b = 2287655;
//Convert back to decimal format for human consumption:
Console.WriteLine("{0} == {1}: {2}", ((double)a)/100, ((double)b)/100, a == b);

希望这有帮助!

答案 4 :(得分:1)

浮点数存储近似值(浮点数的精度约为6-7位小数)。

当您使用fp数字计算时,您通常会在每个数字中产生微小的表示错误,这些错误会被带入计算中。乘法等操作会放大这些错误。如果你不小心,错误会变得很严重。

最常见的问题是使用==来确定两个值是否精确相等,因为2.999999和3.00000000非常接近,但不相等。由于fp表示中的错误,最常见的是结果是接近但不相等的数字,正如您所发现的那样。

因此,我们不得不说“我的号码是否完全等于3.0”,而是说“我的号码是否接近3.0,我对它感到高兴?”。我们通过使用公差值进行测试来执行此操作,如:“我的值是否大于2.999且小于3.001”。在编写数字时,您可以使用类似“{0:0.000}”的格式字符串对其进行舍入并删除它显示的微小错误。

所以你可以通过以下方式达到你想要的效果(精确到3位小数):

Console.WriteLine("{0:0.000} == {1:0.000}: {2}", a, b, Math.Abs(a - b) < 0.0001);

答案 5 :(得分:0)

使用Decimal数据类型而不是Double数据类型。要被视为十进制的数字实数,使用后缀m或M.如果没有后缀m,则将该数字视为double并生成编译器错误。

decimal a = 1117.54M + 8561.64M + 13197.37M;
decimal b = 22876.55M;
Console.WriteLine("{0} == {1}: {2}", a, b, a == b);