【问题标题】:simple floating-point numbers lose precision简单的浮点数失去精度
【发布时间】:2014-05-15 21:28:30
【问题描述】:

我正在使用 Delphi XE2 Update 3。即使是最简单的浮点数(如 3.7)也存在精度问题。鉴于此代码(32 位控制台应用程序):

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses System.SysUtils;

var s: Single; d: Double; x: Extended;
begin
  Write('Size of Single  -----  ');  Writeln(SizeOf(Single));
  Write('Size of Double  -----  ');  Writeln(SizeOf(Double));
  Write('Size of Extended  ---  ');  Writeln(SizeOf(Extended));  Writeln;

  s := 3.7;  d := 3.7;  x := 3.7;

  Write('"s" is ');                  Writeln(s);
  Write('"d" is ');                  Writeln(d);
  Write('"x" is ');                  Writeln(x);                 Writeln;

  Writeln('Single Comparison');
  Write('"s > 3.7"  is  ');          Writeln(s > 3.7);
  Write('"s = 3.7"  is  ');          Writeln(s = 3.7);
  Write('"s < 3.7"  is  ');          Writeln(s < 3.7);           Writeln;

  Writeln('Double Comparison');
  Write('"d > 3.7"  is  ');          Writeln(d > 3.7);
  Write('"d = 3.7"  is  ');          Writeln(d = 3.7);
  Write('"d < 3.7"  is  ');          Writeln(d < 3.7);           Writeln;

  Writeln('Extended Comparison');
  Write('"x > 3.7"  is  ');          Writeln(x > 3.7);
  Write('"x = 3.7"  is  ');          Writeln(x = 3.7);
  Write('"x < 3.7"  is  ');          Writeln(x < 3.7);           Readln;
end.

我得到这个输出:

Size of Single  -----  4
Size of Double  -----  8
Size of Extended  ---  10

"s" is  3.70000004768372E+0000
"d" is  3.70000000000000E+0000
"x" is  3.70000000000000E+0000

Single Comparison
"s > 3.7"  is  TRUE
"s = 3.7"  is  FALSE
"s < 3.7"  is  FALSE

Double Comparison
"d > 3.7"  is  TRUE
"d = 3.7"  is  FALSE
"d < 3.7"  is  FALSE

Extended Comparison
"x > 3.7"  is  FALSE
"x = 3.7"  is  TRUE
"x < 3.7"  is  FALSE

您可以看到extended 是唯一能够正确计算的类型。我认为精度只是使用像3.14159265358979323846 这样的复杂浮点数时的问题,而不是像3.7 这样简单的问题。使用single 时的问题是有道理的。但是为什么double 不起作用?

【问题讨论】:

  • 这应该给你一个short answer。这是一个人类 readable article 和一个 detailed story
  • 3.7 并不简单。 3.75 是。或 3.71875 f.i.如果你想更近一点,或者 3.69921875...
  • 我不敢相信这不是重复的。我进行了快速搜索,但没有找到任何东西。
  • 所有伟大的 cmets,感谢您的文章。
  • 请不要以为你可以只使用extended,突然一切都会好起来的

标签: delphi floating-point-precision


【解决方案1】:

必读:What Every Computer Scientist Should Know About Floating-Point Arithmetic, David Goldberg。

问题不在于精度。相反,问题是可表示性之一。首先,让我们重新定义浮点数用于表示实数。有无限数量的实数。当然,整数也可以这样说。但这里的区别在于,在特定范围内,整数的数量是有限的,而实数的数量是无限的。事实上,就像originally shown by Cantor 一样,任何有限的实数区间都包含不可数的实数值。

所以很明显,我们不能在有限机器上表示所有实数。那么,我们可以表示哪些数字?嗯,这取决于数据类型。 Delphi 浮点数据类型使用二进制表示。单(32 位)和双(64 位)类型符合 IEEE-754 标准。扩展(80 位)类型是 Intel 特定类型。在二进制浮点中,可表示的数字具有 k2n 的形式,其中 k 和 n 是整数。请注意,我并不是说这种形式的所有数字都是可表示的。这是不可能的,因为这样的数字是无限的。相反,我的观点是所有可表示的数字都是这种形式。

可表示的二进制浮点数的一些示例包括:1、0.5、0.25、0.75、1.25、0.125、0.375。您的值 3.7 不能表示为二进制浮点值。

这与您的代码有关的意思是,没有一个代码按照您的预期进行。您希望与值 3.7 进行比较。但是,您正在与最接近的可表示的值进行比较,以 3.7。作为实现细节的问题,这个最接近的可精确表示的值是在扩展精度的上下文中。这就是为什么使用扩展的版本似乎符合您的期望。但是,不要认为这意味着您的变量 x 等于 3.7。事实上,它等于最接近 3.7 的可表示扩展精度值。

Rob Kennedy 的most useful website 可以向您显示与特定数字最接近的可表示值。在 3.7 的情况下,这些是:

3.7 = + 3.70000 00000 00000 00004 33680 86899 42017 73602 98112 03479 76684 57031 25 3.7 = + 3.70000 00000 00000 17763 56839 40025 04646 77810 66894 53125 3.7 = + 3.70000 00476 83715 82031 25

这些以扩展、双、单的顺序呈现。换句话说,这些分别是变量xds 的值。

如果您查看这些值,并将它们与最接近 3.7 的扩展值进行比较,您就会明白为什么您的程序会产生它所产生的输出。这里的单精度和双精度值都大于扩展。这是你的程序告诉你的。

我不想就如何比较浮点值提出任何笼统的建议。做到这一点的最佳方法总是非常关键地取决于具体问题。不能给出有用的一揽子建议。

【讨论】:

  • 很好的解释。与往常一样,您做的不仅仅是回答。谢谢。
  • 不应该k2^n真的是k/2^n吗?
  • @James n 可以是正面的也可以是负面的
【解决方案2】:

简短回答:0.7 无法精确表示(二进制浮点值始终是分母为 2 的幂的分数。);您将其存储在其中的数据类型的精度(以及编译器为您要与之比较的常量类型选择的类型)可能会影响该数字的表示并影响比较。

道德:永远不要直接比较两个浮点值是否相等,除非它们是完全相同的数据类型并分配了相同的确切值。

必填链接:What Every Computer Scientist Should Know About Floating-Point Arithmetic

另一个可能有用的链接是 Delphi 的 Math.SameValue 函数,它允许您根据特定的允许增量(差异)比较两个浮点值以获得近似相等。

【讨论】:

  • 不是数字不齐。可表示为二进制浮点表示形式为 k/2^n。除此之外,我认为您已经掌握了它。
  • @David:感谢您的编辑。关于你提出的观点,你有什么建议可以让非数学家(和新程序员)用简单的语言更好地理解它?
  • 我不确定是否真的有任何方法可以避免至少一点数学。也许最接近的可能是说二进制浮点值始终是分母为 2 的幂的分数。
  • @David:应该可以。我已经进行了相应的编辑(我想不出更好的术语)。感谢您的协助。 (此评论和修订说明中给出的学分。)
  • +1,这也是一个很好的答案。感谢您的专业知识和文章链接。
猜你喜欢
  • 2022-11-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 2018-02-23
  • 2020-02-03
  • 1970-01-01
  • 2015-09-04
  • 2013-12-15
相关资源
最近更新 更多