Чи повинен `Vector <float> .Equals` бути рефлексивним чи він повинен дотримуватися семантики IEEE 754?


9

При порівнянні значень з плаваючою комою для рівності існує два різних підходи:

  • NaNне дорівнює собі, що відповідає специфікації IEEE 754 .
  • NaNрівність собі, що забезпечує математичну властивість рефлексивності, що має істотне значення для визначення відношення еквівалентності

Вбудований в IEEE з плаваючою точкою типу в C # ( floatі double) слід IEEE семантиці ==і !=(і реляційні оператори , як <) , але забезпечити повернення для object.Equals, IEquatable<T>.EqualsCompareTo).

Тепер розглянемо бібліотеку, яка забезпечує векторні структури поверх float/ double. Такий векторний тип би перевантажував ==/ !=і переосмислював object.Equals/ IEquatable<T>.Equals.

З чим усі згодні, це те, що ==/ !=слід слідувати семантиці IEEE. Питання полягає в тому, чи повинна така бібліотека реалізовувати Equalsметод (який є окремим від операторів рівності) таким чином, що рефлексивно або таким чином, що відповідає семантиці IEEE.

Аргументи для використання семантики IEEE для Equals:

  • Звідси випливає IEEE 754
  • Це (можливо, набагато швидше), оскільки він може скористатися інструкціями SIMD

    Я задав окреме запитання щодо stackoverflow про те, як би ви виражали рефлексивну рівність, використовуючи інструкції SIMD та їх вплив на продуктивність: інструкції SIMD для порівняння рівності з плаваючою комою

    Оновлення: Схоже, можна ефективно реалізувати рефлексивну рівність за допомогою трьох інструкцій SIMD.

  • Документація для Equalsне вимагає рефлексивності під час залучення плаваючої точки:

    Наступні твердження повинні бути правдивими для всіх реалізацій методу рівнянь (Object). У списку x, yі zявляють собою посилання на об'єкти, які не є порожніми.

    x.Equals(x)повернення true, за винятком випадків, що стосуються типів з плаваючою комою. Див. ISO / IEC / IEEE 60559: 2011, Інформаційні технології - Мікропроцесорні системи - Арифметика з плаваючою комою.

  • Якщо ви використовуєте поплавці як словникові клавіші, ви живете в стані гріха і не повинні сподіватися на розумну поведінку.

Аргументи рефлексивності:

  • Це узгоджується з існуючими типами, в тому числі Single, Double, Tupleі System.Numerics.Complex.

    Я не знаю жодного прецеденту в BCL, де Equalsслід IEEE, а не рефлексивно. Лічильник приклади включають Single, Double, Tupleі System.Numerics.Complex.

  • Equalsв основному використовується контейнерами та алгоритмами пошуку, які покладаються на рефлексивність. Для цих алгоритмів підвищення продуктивності не має значення, якщо заважає їм працювати. Не жертвуйте правильністю виконання.
  • Він ламає всі хеш на основі наборів і словники, Contains, Find, IndexOfна різних збірках / LINQ, набір на основі LINQ операції ( Union, Exceptі т.д.) , якщо дані містять NaNзначення.
  • Код, який робить фактичні обчислення, де IEEE семантичний прийнятний, зазвичай працює на конкретних типах і використовує ==/ !=(або, скоріше, порівняння epsilon).

    Наразі ви не можете писати обчислення високої продуктивності, використовуючи дженерики, оскільки для цього вам потрібні арифметичні операції, але вони недоступні через інтерфейси / віртуальні методи.

    Так повільніший Equalsметод не вплине на більшість високопродуктивних кодів.

  • Можна запропонувати IeeeEqualsметод або IeeeEqualityComparer<T>для тих випадків, коли вам потрібна семантика IEEE або вам потрібно мати перевагу у виконанні.

На мій погляд, ці аргументи сильно сприяють рефлексивному виконанню.

Команда CoreFX Microsoft планує впровадити такий векторний тип у .NET. На відміну від мене, вони віддають перевагу рішенню IEEE , головним чином завдяки перевагам у роботі. Оскільки таке рішення, безумовно, не буде змінено після остаточного випуску, я хочу отримати відгук громади про те, що я вважаю великою помилкою.


1
Відмінне і викликає думки питання. Для мене (принаймні) це не має сенсу ==і Equalsповертає різні результати. Багато програмістів припускають, що вони є, і роблять те саме . Крім того - загалом, реалізація операторів рівності посилається на Equalsметод. Ви стверджували, що можна включити a IeeeEquals, але можна також зробити це навпаки і включити ReflexiveEquals-метод. Цей Vector<float>тип може бути використаний у багатьох критично важливих для роботи додатках, і його слід оптимізувати відповідно.
die maus

@diemaus Деякі причини, чому я не вважаю переконливими: 1) для float/ doubleта декількох інших типів, ==і Equalsвже різні. Я думаю, що невідповідність існуючим типам буде навіть більш заплутаною, ніж невідповідність між ними, ==і Equalsвам доведеться все-таки мати справу з іншими типами. 2) В основному всі загальні алгоритми / колекції використовують Equalsі покладаються на його рефлексивність до функціонування (LINQ та словники), тоді як конкретні алгоритми з плаваючою комою зазвичай використовують ==там, де вони отримують свою семантику IEEE.
CodesInChaos

Я вважав Vector<float>би іншого «звіра», ніж простого floatабо double. За цією мірою я не бачу причин Equalsабо ==оператор дотримуватись цих стандартів. Ви самі сказали: "Якщо ви використовуєте поплавці як клавіші словника, ви живете в стані гріха і не повинні очікувати розумної поведінки". Якщо потрібно зберігати NaNв словнику, то це їхня сама проклята вина за використання жахливої ​​практики. Я навряд чи думаю, що команда CoreFX не продумала цього. Я б поїхав з ReflexiveEqualsчи подібним, просто заради виконання.
die maus

Відповіді:


5

Я заперечую, що поведінка IEEE є правильною. NaNs ні в якому разі не рівноцінні один одному; вони відповідають погано визначеним умовам, коли числова відповідь не підходить.

Крім переваг від продуктивності, що виникають від використання арифметики IEEE, яку підтримує більшість процесорів, я думаю, що існує семантична проблема з тим, що, якщо isnan(x) && isnan(y), тоді x == y. Наприклад:

// C++
double inf = std::numeric_limits<double>::infinity();
double x = 0.0 / 0.0;
double y = inf - inf;

Я заперечую, що немає жодної значущої причини, яку можна вважати xрівним y. Ви навряд чи зможете зробити висновок, що вони є еквівалентними числами; вони взагалі не є цифрами, тому це просто здається цілком недійсним поняттям.

Крім того, з точки зору дизайну API, якщо ви працюєте над бібліотекою загального призначення, яка призначена для використання багатьма програмістами, просто має сенс використовувати найбільш типову для галузі галузь семантику з плаваючою комою. Мета гарної бібліотеки - заощадити час для тих, хто цим користується, тому побудова нестандартної поведінки дозріла для плутанини.


3
Те, що NaN == NaNповинно повернути помилкове, є безперечним. Питання в тому, що .Equalsметод повинен робити. Наприклад, якщо я використовую NaNв якості словника ключ, пов'язане значення стає безповоротним, якщо NaN.Equals(NaN)повертається помилковим.
CodesInChaos

1
Я думаю, що вам доведеться оптимізувати для загальної справи. Загальним випадком для вектора чисел є чисельність з високою пропускною здатністю (часто оптимізована за допомогою інструкцій SIMD). Я заперечую, що використання вектора як ключового словника є надзвичайно рідкісним випадком використання, і навряд чи варто розробляти свою семантику навколо. Контраргумент , що представляється найбільш розумним для мене консистенція, так як існуючі Single, Doubleі т.д. класи вже мають рефлекторне поведінка. ІМХО, це було просто неправильне рішення. Але я б не дозволив елегантності заважати корисності / швидкості.
Джейсон Р

Але зазвичай використовуються числові обчислення, ==які завжди дотримувалися IEEE, тому вони отримували швидкий код незалежно від того, як Equalsреалізовано. ІМО вся справа в окремому Equalsметоді використовується в алгоритмах, які не цікавляться конкретного типу, наприклад, функції LINQ Distinct().
CodesInChaos

1
Я це розумію. Але я б заперечував проти API, який має ==оператора та Equals()функцію, що мають різну семантику. Я думаю, що ви платите витрати на потенційну плутанину з точки зору розробника, без реальної вигоди (я не присвоюю жодного значення тому, щоб мати можливість використовувати вектор чисел як ключ словника). Це лише моя думка; Я не думаю, що існує об'єктивна відповідь на питання.
Джейсон R

0

Виникає проблема: IEEE754 визначає відносні операції та рівність способом, який добре підходить для чисельних застосувань. Він не підходить для сортування та хешування. Отже, якщо ви хочете сортувати масив на основі числових значень або якщо ви хочете додати числові значення до набору або використовувати їх як ключі у словнику, ви або заявляєте, що значення NaN заборонені, або ви не використовуєте IEEE754 вбудовані операції. Ваша хеш-таблиця повинна переконатися, що всі NaN збігаються з однаковим значенням, і порівняти однакові між собою.

Якщо Ви визначаєте вектор, то Ви повинні прийняти проектне рішення, чи бажаєте Ви використовувати його лише для числових цілей, чи він повинен бути сумісним із сортуванням та хешированием. Я особисто вважаю, що числова мета повинна бути набагато важливішою. Якщо потрібне сортування / хешування, то ви можете написати клас із Vector як член та визначити хешування та рівність у тому класі, як вам подобається.


1
Я згоден, що числові цілі важливіші. Але ми вже маємо ==та !=операторів для них. На мій досвід, Equalsметод в значній мірі використовується тільки нечисловими алгоритмами.
CodesInChaos
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.