Почему 24.0000 не равно 24.0000 в MATLAB?


Я пишу программу, где мне нужно удалить дубликаты точек, хранимых в матрице. Проблема в том, что когда дело доходит до проверки, находятся ли эти точки в матрице, 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

две точки (vertex1 и vertex2) должны быть исключены из результата. Это должно быть сделано следующие команды:

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
6 56

6 ответов:

проблема у вас возникли относится к тому, как числа с плавающей точкой представлены на компьютере. Более подробное обсуждение представлений с плавающей запятой появляется в конце моего ответа (раздел" представление с плавающей запятой"). Элемент TL; DR версия: поскольку компьютеры имеют конечное количество памяти, числа могут быть представлены только с конечной точностью. Таким образом, точность чисел с плавающей запятой ограничена определенным числом десятичных знаков места (около 16 значащих цифр для двойной точности значения по умолчанию используется в MATLAB).

фактическая и отображаемая точность

Теперь рассмотрим конкретный пример в вопросе... пока 24.0000 и 24.0000 are отображается таким же образом, оказывается, что они на самом деле отличаются очень маленькими десятичными суммами в этом случае. Вы не видите его, потому что MATLAB только показывает 4 значащих цифры мимо по умолчанию, сохраняя общий дисплей аккуратным и аккуратным. если вы хотите увидеть полную точность, вы должны либо выдать format long команда или просмотр a шестнадцатеричное представление номер:

>> 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 для арифметики с плавающей запятой) составляет Что Каждый Компьютерный Ученый Должен Знать Об Арифметике С Плавающей Запятой Дэвид Голдберг.

двоичное число с плавающей запятой на самом деле представлено тремя целыми числами: знаковый бит s, знаменатель (или коэффициент/дробь) b, и экспоненты e. для формата двойной точности с плавающей запятой, каждое число представлено 64 битами, расположенными в памяти следующим образом:

enter image description here

реальное значение может быть найдено с помощью следующей формулы:

enter image description here

этот формат позволяет представлениях чисел в диапазоне от 10^-308 до 10^308. Для MATLAB вы можете получить эти ограничения от realmin и realmax:

>> 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], вычислить среднее, а затем вычесть среднее смещение для сравнения, мы получаем значение, которое отличается для последних 3 значащих цифр. Это иллюстрирует, как смещение или масштабирование данных может изменить точность вычислений, выполняемых на нем, что-то, что должно быть учтено с определенными проблемами.

посмотрите на эту статью: опасности с плавающей точкой. Хотя его примеры находятся в FORTRAN, он имеет смысл практически для любого современного языка программирования, включая MATLAB. Ваша проблема (и решение для нее) описана в разделе "Безопасные сравнения".

тип

format long g

эта команда покажет полное значение числа. Это, вероятно, будет что-то вроде 24.00000021321 != 24.00000123124

попробуйте написать

0.1 + 0.1 + 0.1 == 0.3.

внимание: вы можете быть удивлены результатом!

может быть, эти два числа действительно 24.0 и 24.000000001, но вы не видите все десятичные знаки.

Проверьте функция MATLAB EPS.

Matlab использует математику с плавающей запятой до 16 цифр точности (отображаются только 5).