Являются ли операции с плавающей запятой результатом бесконечности неопределенного поведения для типов с плавающей точкой IEC 559 / IEEE 754 - PullRequest
10 голосов
/ 12 мая 2019

Я читал Бесконечность, а не constexpr , что, кажется, указывает на то, что создание бесконечности - неопределенное поведение:

[выражение] / 4

Если во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение не определено.

Однако, если std::numeric_limits::is_iec559 равно true, это дает нам больше гарантий.

Приведенный ниже код использует эту гарантию для создания бесконечного числа. При выполнении в контексте constexpr это приводит к сбою компилятора, поскольку undefined behavior в случае, если is_iec559 равно false.

// clang++ -std=c++17 -O3
#include <limits>

constexpr double createInfinity()
{
    static_assert(std::numeric_limits<double>::is_iec559, "asdf");
    double d = 999999999999;
    while (d != std::numeric_limits<double>::infinity())
    {
        d *= d;
    }
    return -1*d;
}

static_assert(createInfinity() == std::numeric_limits<double>::infinity(), "inf");

Код в проводнике компилятора

Поскольку эта функция всегда приводит к бесконечности, ее нельзя вызывать в допустимой программе на C ++. Однако, как мы утверждаем на is_iec559, мы получаем дополнительные гарантии. Эта программа все еще недействительна?

  • Если неверно? Какой смысл иметь is_iec559?
  • Если действительно? Почему он действителен во время выполнения, а не в контексте constexpr?

(Ответы могут использовать оба C ++ 17 в качестве предстоящего C ++ 20, пожалуйста, четко укажите, какой используется)

Ответы [ 2 ]

1 голос
/ 07 июля 2019

Ожидание некоторого времени иногда помогает, похоже, что Clang получил патч, который делает этот код компилируемым: https://reviews.llvm.org/D63793

До r329065 мы использовали [-max, max] в качестве диапазонапредставимые значения, потому что fptrunc LLVM не гарантировал определенного поведения при усечении от большего типа с плавающей точкой к меньшему.Теперь, когда это было исправлено, мы можем заставить clang следовать нормальной семантике IEEE 754 в этом отношении и принять больший диапазон [-inf, + inf] в качестве диапазона представимых значений.

Интересный элемент, который следует отметить(часть комментариев кода в этой ревизии) заключается в том, что операции, приводящие к NaN, (пока) недопустимы:

// [expr.pre]p4:
//   If during the evaluation of an expression, the result is not
//   mathematically defined [...], the behavior is undefined.
// FIXME: C++ rules require us to not conform to IEEE 754 here.

Пример в проводнике компилятора:

#include <limits>

constexpr double createNan()
{
    static_assert(std::numeric_limits<double>::is_iec559, "asdf");
    double d = std::numeric_limits<double>::infinity() / std::numeric_limits<double>::infinity();
    return -1*d;
}

static_assert(createNan() != 0., "NaN");
1 голос
/ 13 мая 2019

Программа некорректна.

За [expr.const] / 4 ,

Выражение e является выражением основной константыесли при оценке e, следуя правилам абстрактной машины, не будет вычислено одно из следующих выражений:

  • [...]

  • операция, которая будет иметь неопределенное поведение, как указано в [intro] - [cpp] этого документа [ Примечание: включая, например, целочисленное переполнение со знаком ( [expr.prop] ), определенную арифметику указателя ( [expr.add] ), деление на ноль или определенные операции сдвига - конечная нота ];

  • [...]

И [expr.pre] /4 говорит:

Если во время вычисления выражения результат не определен математически или не находится в диапазоне представляемых значений для его типа, поведение не определено. [ Примечание: Обработка деления на ноль, формирования остатка с использованием делителя нуля и всех исключений с плавающей запятой варьируется в зависимости от машины и иногда настраивается библиотечной функцией.- конечная нота ]

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


Интересно, что numeric_limits<double>::infinity() равно constexpr.Это отлично.По [numeric.limits] / infinity :

static constexpr T infinity() noexcept;

Представление положительной бесконечности, если доступно.

Имеет значение для всех специализаций, для которых has_­infinity != false,Требуется в специализациях, для которых is_­iec559 != false.

Если is_iec559 == true, то has_infinity == true и возвращается значение бесконечности.Если is_iec559 == false, has_infinity может быть true, то в этом случае также возвращается значение бесконечности, или оно может быть false, и в этом случае infinity() возвращает 0.(!)

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

...