Почему 2 не равно √2 * √2?

Я заметил, что согласно Lua, 2 ~= math.sqrt(2) ^ 2

print(2 == math.sqrt(2) ^ 2) --> false
print(2, math.sqrt(2) ^ 2) --> 2  2

Почему это происходит?


person David    schedule 20.11.2014    source источник
comment
Скорее всего неточность с плавающей запятой.   -  person Thilo    schedule 20.11.2014
comment
Я бы не считал это точной копией связанных вопросов и предложил бы оставить этот вопрос открытым. Как я объясню в своем ответе ниже, за наблюдением, сделанным в вопросе, стоит определенный принцип.   -  person njuffa    schedule 21.11.2014


Ответы (3)


Большинство чисел с плавающей запятой не могут быть сохранены именно в числовом типе Lua (по умолчанию C double). math.sqrt(2) является одним из них.

Если вы пытаетесь:

print(2 - math.sqrt(2) ^ 2)

Выход: -4.4408920985006e-016, что является очень маленьким числом, но тем не менее делает два числа не совсем эквивалентными.


Эта проблема точности с плавающей запятой существует не только в Lua, но и во многих других языках. Как комментирует @Thilo, вы можете использовать небольшую «дельту» при сравнении на равенство на практике. Вам могут быть интересны: Часто задаваемые вопросы по языку C: как проверить плавающее положение "достаточно близко"? равенство точек?.

person Yu Hao    schedule 20.11.2014
comment
Способ обойти это — разрешить небольшую дельту при сравнении на равенство. Выбор того, насколько большой должна быть эта дельта, вероятно, зависит от приложения. - person Thilo; 20.11.2014
comment
Число с плавающей запятой — это то, почему значения sqrt(2)^2 не равны 2, но тот факт, что когда вы печатаете, он говорит 2 из-за неточности метода tostring. Вы можете напечатать гораздо более точные числа, такие как tostring(0.000000000000000000001) -> 1e-021 - person doog abides; 20.11.2014
comment
@doogabides Конечно, если число равно double в C, Lua использует формат "%.14g" для печати чисел по умолчанию, если я правильно помню. - person Yu Hao; 20.11.2014
comment
@ yu-hao Верно, я просто добавлял к вашему хорошему ответу, так как он все еще мог быть сбит с толку выводом на печать с 2 2 - person doog abides; 20.11.2014

Следующее рассуждение показывает, что в общем случае sqrt(x)*sqrt(x) == x невозможно удерживать для всех операндов в арифметике с плавающей запятой, будь то двоичная или десятичная, даже если операция извлечения квадратного корня возвращает правильно округленные результаты (как это обычно бывает на современных компьютерах). которые соответствуют стандарту IEEE-754 с плавающей запятой).

В двоичном представлении с плавающей запятой конечной точности каждая бинада (пространство между двумя последовательными степенями числа 2) содержит одно и то же количество точно представляемых чисел. Для одинарной точности IEEE-754 на двоичную группу приходится 223 таких машинных номеров, для двойной точности IEEE-754 таких номеров на одну двоичную группу приходится 252.

Квадратный корень — это сжимающая операция, поскольку она отображает входную область из двух бинад, скажем, [1,4], в результирующий диапазон только из одного бинада, скажем, [1,2). Таким образом, в случае операндов двойной точности IEEE-754 253 возможных аргументов функции в двух бинадах сопоставляются не более чем с 252 различными результатами квадратного корня. Если теперь возвести в квадрат результаты операции извлечения квадратного корня, мы получим не более 252 различных произведений, распределенных по двум двоичным числам, которые могут представлять 253 числа машин.

Следовательно, равенство sqrt(x)*sqrt(x) == x может выполняться не более чем для половины возможных входных данных с плавающей запятой. Экспериментально можно показать, что оно выполняется ровно для половины входов от двух бинад.

С другой стороны, равенство x == sqrt (x * x) выполняется в арифметике с плавающей запятой IEEE-754 при условии, что при вычислении продукта нет промежуточного переполнения или потери значимости.

person njuffa    schedule 21.11.2014

Природа арифметики с плавающей запятой очень важна в общих средах программирования. Программы статического анализа исходного кода классического C (например, «lint») фактически выдадут вам предупреждение, если вы используете реляционные операторы для чисел с плавающей запятой. Как уже говорили другие, мы допускаем терпимость в сравнениях.

Однако пример, который вы выбрали, √2, иррационален. Никакая вычислительная система счисления не может удержать это число. Оно может быть точным только как символическое выражение. Доступно несколько систем символьного программирования: Octave-Symbolic, MathCAD, MATLAB, ... и теперь Wolfram (я думаю). Они сохранят √2 как выражение и оценят √2 * √2 как ровно 2.

person Tom Blodget    schedule 22.11.2014
comment
Один из способов точного представления √2 — это алгебраическое число. Они определяются как решения многочлена. Рациональные числа p/q можно представить в виде многочлена q x-p=0, решением которого является p/q. Квадратный корень из 2 можно представить в виде многочлена x^2-2=0. Алгебраические числа обладают хорошими свойствами, их можно складывать, умножать и делить. - person Salix alba; 23.11.2014