Стандартный способ определения отношения эквивалентности с плавающей запятой - программирование

Стандартный способ определения отношения эквивалентности с плавающей запятой

Я знаю обычные проблемы с арифметикой с плавающей запятой и точностью потери, поэтому это не обычный вопрос о том, почему 0.1 + 0.2 != 0.3 и т.п.

Вместо этого я действительно хотел бы реализовать двоичный предикат в С++ (со 100% стандартным способом), который фактически реализует реальный математический эквивалентность (т.е. рефлексивный, транзитивный и симметричный), так что два двойника находятся в одном классе эквивалентности, если они представляют собой то же самое значение во всех отношениях, выделяя такие угловые случаи, как 0.0 и -0.0, но рассматривая все значения NaN как находящиеся в одном классе эквивалентности. (В частности, значение по умолчанию == не то, что я хочу, потому что оно не рефлексивно в случае NaN и не различает 0.0 и negative -0.0, что я хотел бы быть в разных классы эквивалентности, поскольку они фактически являются разными значениями и приводят к различным поведениям во время выполнения).

Какой самый короткий и простой способ сделать это, который не зависит от типа punning или каким-либо образом определенного поведением? Пока у меня есть:

#include <cmath>

bool equiv(double x, double y)
{   
    return (x == y && (x != 0.0 || std::signbit(x) == std::signbit(y))) ||
           (std::isnan(x) && std::isnan(y));
}

Я считаю, что это обрабатывает угловые случаи, о которых я знаю и описывал ранее, но есть ли какие-либо другие угловые случаи, когда это не обрабатывает то, что мне не хватает? И является ли указанный выше двоичный предикат гарантированным для определения отношения эквивалентности в соответствии со стандартом С++ или является ли какое-либо поведение неопределенным, определяемым реализацией и т.д.?

4b9b3361

Ответ 1

Выглядит правильно.

Фактически вы можете избавиться от вызовов функций для платформ, реализующих IEEE 754 (Intel, Power и ARM), поскольку специальные значения с плавающей запятой могут быть определены без вызовов.

bool equiv(double x, double y) {
    return (x == y && (x || (1 / x == 1 / y))) || (x != x && y != y);
}

В приведенном выше примере используется тот факт, что IEEE:

  • Отклонение от нуля до нуля дает бесконечные специальные значения, сохраняющие знак. Следовательно, 1 / -0. дает -infinity. Особые значения Infinity с одинаковым знаком сравниваются.
  • NaN не сравниваются.

Оригинальная версия, тем не менее, читается лучше для большинства людей. Судя по опыту собеседования, не каждый разработчик знает, как возникают и ведут себя особые значения с плавающей запятой.

Если бы только NaNs имели одно представление, вы могли бы просто сделать memcmp.


Что касается стандартов языка С++ и C, В книге New C Standard говорится:

Часто слышен термин "плавающая точка" IEEE. Это стало возможным благодаря тому, что оригинальные стандарты по этой теме были опубликованы IEEE. Этот стандарт для двоичной арифметики с плавающей запятой - это то, что многие хост-процессоры предоставляют более десяти лет. Однако его использование не санкционировано C99.

Представление для двоичной с плавающей запятой, указанное в этом стандарте, используется семейством процессоров Intel x86, Sun SPARC, HP PA-RISC, IBM P OWER PC, HP - DEC - Alpha, а большинство современных процессоров ( некоторые процессоры DSP поддерживают подмножество или делают небольшие изменения по причинам затрат/производительности, в то время как другие имеют более существенные отличия, например, TMS320C3x использует два дополнения). Существует также общедоступная программная реализация этого стандарта.

Другие представления по-прежнему поддерживаются процессорами (IBM 390 и HP - DEC - VAX), имеющими существующую клиентскую базу, которая предшествует публикации документов, на которых основан этот стандарт. Эти представления, вероятно, будут продолжать поддерживаться в течение некоторого времени из-за существующего кода, который полагается на нем (IBM 390 и HP-DEC-Alpha поддерживали как свои компании, так и старые представления и требования IEC 60559).

Существует общее мнение, что после того, как стандарт IEC 60559 был указан, все его требуемые функциональные возможности будут обеспечиваться соответствующими реализациями. Возможно, что зависимости программ C от конструкций IEC 60559, которые могут различаться между реализациями, не будут задокументированы из-за этого общего неправильного убеждения (лицо, пишущее документацию, не всегда является человеком, знакомым с этим стандартом).

Как и стандарт C, стандарт IEC 60559 не полностью определяет поведение каждой конструкции. Он также предоставляет необязательное поведение для некоторых конструкций, например, при повышении уровня недополнения, и имеет необязательные конструкции, которые реализация может использовать или не использовать, например, двойной стандарт. C99 не всегда предоставляет метод для определения поведения реализации в этих необязательных областях. Например, не существует стандартных макросов, описывающих различные параметры обработки нижнего потока.

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

Языки и компиляторы

Неоднозначность

В идеале, определение языка должно определять семантику языка достаточно точно, чтобы доказать утверждения о программах. Хотя это обычно верно для целочисленной части языка, определения языка часто имеют большую серое пространство, когда речь идет о плавающей точке. Возможно, это связано с тем, что многие разработчики языка считают, что ничего не может быть доказано о плавающей запятой, поскольку она влечет за собой ошибку округления. Если это так, предыдущие разделы продемонстрировали ошибочность в этом рассуждении. В этом разделе обсуждаются некоторые общие серые области в определениях языков, включая предложения о том, как с ними бороться.

... Еще одна двусмысленность в большинстве определений языков касается того, что происходит при переполнении, потоке и других исключениях. Стандарт IEEE точно определяет поведение исключений, и поэтому языки, использующие стандарт как модель, могут избежать какой-либо двусмысленности в этой точке.

... Другая серая область касается интерпретации круглых скобок. Из-за ошибок округления ассоциативные законы алгебры не обязательно имеют место для чисел с плавающей запятой... Независимо от того, указывает ли стандарт языка, что круглые скобки должны быть выполнены, (x + y) + z может иметь совершенно другой ответ чем x + (y + z), как обсуждалось выше.

.... округление может быть проблемой. Стандарт IEEE определяет округление очень точно, и это зависит от текущего значения режимов округления. Это иногда противоречит определению неявного округления в преобразованиях типов или явной круглой функции на языках.

Языковые стандарты не могут указывать результаты операций с плавающей запятой, поскольку, например, можно изменить режим округления во время выполнения, используя std::fesetround.

Поэтому языки C и С++ не имеют другого выбора, кроме как сопоставлять операции с типами с плавающей запятой непосредственно с аппаратными инструкциями и не мешать, как и они. Следовательно, языки не копируют стандарт IEEE/IEC и не наделяют его мандатом.