为什么在MATLAB中,24.0000不等于24.0000?

71

我正在编写一个程序,需要删除存储在矩阵中的重复点。问题是当检查这些点是否存在于矩阵中时,MATLAB无法识别它们。

下面的代码中,intersections函数获取交点:

[points(:,1), points(:,2)] = intersections(...
    obj.modifiedVGVertices(1,:), obj.modifiedVGVertices(2,:), ...
    [vertex1(1) vertex2(1)], [vertex1(2) vertex2(2)]);

结果:

>> points
points =
   12.0000   15.0000
   33.0000   24.0000
   33.0000   24.0000

>> vertex1
vertex1 =
    12
    15

>> vertex2    
vertex2 =
    33
    24

以下命令应该将结果中的两个点(vertex1vertex2)排除:

points = points((points(:,1) ~= vertex1(1)) | (points(:,2) ~= vertex1(2)), :);
points = points((points(:,1) ~= vertex2(1)) | (points(:,2) ~= vertex2(2)), :);

做完这件事之后,我们得到了意料之外的结果:

>> points
points =
   33.0000   24.0000

结果应该是一个空矩阵。正如您所看到的,第一(或第二?)对[33.0000 24.0000]已被消除,但第二个没有。

然后我检查了这两个表达式:

>> points(1) ~= vertex2(1)
ans =
     0
>> points(2) ~= vertex2(2)
ans =
     1   % <-- It means 24.0000 is not equal to 24.0000?

问题是什么?


更令人惊讶的是,我创建了一个只包含这些命令的新脚本:

points = [12.0000   15.0000
          33.0000   24.0000
          33.0000   24.0000];

vertex1 = [12 ;  15];
vertex2 = [33 ;  24];

points = points((points(:,1) ~= vertex1(1)) | (points(:,2) ~= vertex1(2)), :);
points = points((points(:,1) ~= vertex2(1)) | (points(:,2) ~= vertex2(2)), :);

结果如预期:

>> points
points =  
   Empty matrix: 0-by-2

1
这个问题也在这里得到了解决。 - ChrisF
2
@Kamran:抱歉我没有在你问关于比较值的另一个问题时指出浮点数比较的危险。我当时没有立即意识到你可能会遇到这个问题。 - gnovice
2
顺便提一下,比较一下 1.2 - 0.2 - 1 == 01.2 - 1 - 0.2 == 0。很惊讶吧?当你处理浮点数时,操作的顺序很重要。 - jub0bs
1
@Tick Tock:作为问题的提出者,我甚至无法理解您为我的问题选择的标题。此外,它并没有反映出当您打印变量时,MATLAB不会显示整个浮点数部分的事实。 - Kamran Bigdely
1
@m7913d:来自重复规则的解释:“通常,最近的问题会被关闭为旧问题的重复。” - Kamran Bigdely
显示剩余10条评论
6个回答

100
您遇到的问题与计算机上浮点数的表示方式有关。有关浮点表示的更详细讨论在我的答案末尾(“浮点表示”部分)中出现。简而言之:因为计算机具有有限的存储器,数字只能以有限的精度表示。因此,浮点数的准确性仅限于一定数量的小数位数(对于双精度值,即MATLAB中使用的默认值,约为16个有效数字)。
实际精度与显示精度
现在来解决问题中的具体例子... 虽然24.000024.0000以相同的方式显示,但在这种情况下它们实际上略微有很小的小数差异。您没有看到它,因为MATLAB 默认仅显示4个有效数字,保持整体显示整洁。如果要查看完整的精度,应该发出format long命令或查看数字的十六进制表示
>> pi
ans =
    3.1416
>> format long
>> pi
ans =
   3.141592653589793
>> num2hex(pi)
ans =
400921fb54442d18

初始化值与计算值

由于浮点数只能表示有限的值,因此计算可能会得出落在两个这些表示之间的值。在这种情况下,结果必须舍入为其中一个值。这会引入小的机器精度误差。这也意味着直接初始化值或通过某些计算来初始化值可能会给出略微不同的结果。例如,值0.1没有精确的浮点表示(即它被略微舍入),因此由于舍入误差的积累方式,您会得到反直觉的结果:

>> a=sum([0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]);  % Sum 10 0.1s
>> b=1;                                               % Initialize to 1
>> a == b
ans =
  logical
   0                % They are unequal!
>> num2hex(a)       % Let's check their hex representation to confirm
ans =
3fefffffffffffff
>> num2hex(b)
ans =
3ff0000000000000

如何正确处理浮点数比较

由于浮点数值可能存在微小差异,因此应该通过检查这些值是否在某个范围(即容差)内而不是完全相等来进行比较。例如:

a = 24;
b = 24.000001;
tolerance = 0.001;
if abs(a-b) < tolerance, disp('Equal!'); end

会显示"相等!"。

然后,您可以将代码更改为以下内容:

points = points((abs(points(:,1)-vertex1(1)) > tolerance) | ...
                (abs(points(:,2)-vertex1(2)) > tolerance),:)

浮点数表示

关于浮点数(特别是IEEE 754浮点运算标准)的一个很好的概述是David Goldberg所写的计算机科学家应该了解的浮点运算知识

一个二进制浮点数实际上由三个整数表示:符号位s,尾数(或系数/分数)b和指数e对于双精度浮点格式,每个数字由64位在内存中表示如下:

enter image description here

接下来可以使用以下公式找到实际值:

enter image description here

这种格式允许表示范围在10^-308到10^308之间的数字。对于MATLAB,您可以从realminrealmax获取这些限制:
>> realmin
ans =
    2.225073858507201e-308
>> realmax
ans =
    1.797693134862316e+308

由于浮点数表示使用有限数量的位,因此在上述给定范围内只能表示有限数量的数字。计算通常会产生一个值,该值与这些有限表示中的任何一个都不完全匹配,因此必须四舍五入。这些机器精度误差以不同的方式表现出来,如上面的例子所讨论的那样。
为了更好地理解这些舍入误差,查看函数eps提供的相对浮点精度很有用,它量化了给定数字到下一个最大浮点表示的距离:
>> eps(1)
ans =
     2.220446049250313e-16
>> eps(1000)
ans =
     1.136868377216160e-13

注意精度是相对于所表示的数字大小而言的;较大的数字将在浮点表示之间有更大的距离,因此小数点后面的有效数字将更少。这在某些计算中可能是一个重要考虑因素。请考虑以下示例:
>> format long              % Display full precision
>> x = rand(1, 10);         % Get 10 random values between 0 and 1
>> a = mean(x)              % Take the mean
a =
   0.587307428244141
>> b = mean(x+10000)-10000  % Take the mean at a different scale, then shift back
b =
   0.587307428244458

请注意,当我们将x的值从范围[0 1]移动到范围[10000 10001]时,计算平均值,然后减去平均偏移量进行比较,得到的值与最后三个有效数字不同。这说明数据的偏移或缩放可以改变对其执行的计算的准确性,这是某些问题必须考虑的事情。

2
你可以在矩阵视图中查看变量。右键单击变量 -> “查看选择”或类似选项?我这里没有MATLAB,所以无法检查。 - atsjoo
5
在命令提示符处输入“format long”,您还可以看到一些细微的差异。 - gnovice
Matlab的精度约为16位数字...除非您执行上述操作,否则仅显示5位。 - jle
2
你是对的: 格式 长 点 = 12.000000000000000 15.000000000000000 33.000000000000000 23.999999999999996 33.000000000000000 24.000000000000000 - Kamran Bigdely
7
在这种情况下,“格式化为十六进制”有时比“格式化为长整型”更有效。 - Sam Roberts
显示剩余2条评论

22
看看这篇文章:浮点数的危险性。尽管它的例子是用FORTRAN编写的,但对于几乎任何现代编程语言,包括MATLAB,都有意义。你的问题(以及解决方案)在“安全比较”部分中有描述。

1
我一段时间前发现了它,对它印象非常深刻 =) 现在在类似的情况下,我总是推荐它。 - Rorick
这是一份关于编程的相关内容,需要将其翻译成中文。请仅返回已翻译的文本:这是一个优秀资源的存档版本! - wizclown

13

类型

format long g

这个命令将显示数字的完整值。它可能是类似于24.00000021321 != 24.00000123124的数字。


7

尝试写出以下代码:

0.1 + 0.1 + 0.1 == 0.3.

警告:你可能会对结果感到惊讶!


我尝试了一下,它返回0。但我不明白它与上述问题有什么关系。你能解释一下吗? - Max
6
这是因为0.1存在一些浮点误差,当你将三个这样的数相加时,误差不一定会合并成0。同样的问题导致(浮点数)24不完全等于(另一个浮点数)24。 - Derek

2
也许这两个数字确实是24.0和24.000000001,但你没有看到所有的小数位。

1

看看Matlab EPS函数

Matlab使用浮点数运算,精度高达16位(只显示5位)。


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