Считается ли нормальным, что f = NAN может вызывать исключение с плавающей запятой?
C2x (как и предыдущий):
Макрос определяется тогда и только тогда, когда реализация поддерживает тихие NaN для
float
тип. Он расширяется до постоянного выражения типа float, представляющего тихий NaN.
Пример кода (t0a.c)
#include <stdio.h>
#include <math.h>
#include <fenv.h>
#if _MSC_VER && ! __clang__ && ! __INTEL_COMPILER
#pragma fenv_access (on)
#else
#pragma STDC FENV_ACCESS ON
#endif
void print_fe_excepts_raised(void)
{
printf("exceptions raised ");
if (fetestexcept(FE_DIVBYZERO)) printf(" FE_DIVBYZERO");
if (fetestexcept(FE_INEXACT)) printf(" FE_INEXACT");
if (fetestexcept(FE_INVALID)) printf(" FE_INVALID");
if (fetestexcept(FE_OVERFLOW)) printf(" FE_OVERFLOW");
if (fetestexcept(FE_UNDERFLOW)) printf(" FE_UNDERFLOW");
if (fetestexcept(FE_ALL_EXCEPT)==0) printf(" none");
printf("\n");
}
int main(void)
{
float f;
feclearexcept(FE_ALL_EXCEPT);
f = NAN;
print_fe_excepts_raised();
(void)f;
return 0;
}
Призывы:
# msvc (version 19.29.30133 for x64)
$ cl t0a.c /std:c11 /Za /fp:strict && t0a.exe
exceptions raised FE_INEXACT FE_INVALID FE_OVERFLOW
# clang on Windows (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.exe
exceptions raised FE_INEXACT FE_INVALID FE_OVERFLOW
# gcc on Windows (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra && ./a.exe
exceptions raised none
# gcc on Linux (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra && ./a.out
exceptions raised none
# clang on Linux (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.out
exceptions raised none
Для msvc и clang в Windows: это потому, что:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:94:9
#define NAN ((float)(INFINITY * 0.0F))
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:90:9
#define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:87:13
#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow
Здесь мы видим, что
NAN
"расширяется до постоянного выражения типа float, представляющего тихий NaN". Это означает, что это может вызвать исключения с плавающей запятой. Тем не мение,
f = NAN
обычно рассматривается как «запись в память». Следовательно, люди могут задаться вопросом: «Как запись в память может вызывать исключения с плавающей запятой?».
1 ответ
Для справки, это...
Макрос определяется тогда и только тогда, когда реализация поддерживает тихие NaN для
float
тип. Он расширяется до постоянного выражения типа float, представляющего тихий NaN.
... это C17 7.12/5, и он, вероятно, имеет такую же или аналогичную нумерацию в C2x.
Обновлено
Тот факт, что при использовании с MSVC или Clang в Windows ваша тестовая программа вызывает исключение FP, предполагает, что комбинация
- Стандартная библиотека Microsoft C и среда выполнения с
- компиляторы MSVC и Clang и
- параметры, которые вы указываете для тех
вызывает генерацию сигнального NaN и его использование в качестве арифметического операнда. Я согласен, что это неожиданный результат, вероятно, указывающий на то, что эти комбинации не полностью соответствуют спецификации языка C в этой области.
не имеет ничего общего с тем, является ли результирующий NaN тихим или сигнальным. Неправильное представление состоит в том, что флаг будет поднят только в результате генерации или обработки сигнального NaN. Это не относится к делу.
Во-первых, IEEE-754 не определяет ни одного случая, в котором генерируется сигнальный NaN. Все определенные операции, которые создают NaN, создают тихий NaN, включая операции, в которых один операнд является сигнальным NaN (поэтому MSVC и Clang в Windows почти наверняка производят тихий NaN в качестве значения макроса). Большинство операций с хотя бы одним сигнальным NaN в качестве операнда по умолчанию вызывают поднятие флага FE_INVALID, но это не обычная причина поднятия этого флага.
Скорее, при обработке исключений по умолчанию флаг поднимается просто из-за запроса на вычисление операции без определенного результата, такого как бесконечность, умноженная на 0. Результатом будет тихий NaN. Обратите внимание, что это не включает операции по крайней мере с одним операндом NaN, которые имеют определенный результат: тихий NaN во многих случаях, неупорядоченный/ложный для сравнений и другие результаты в нескольких случаях.
Учитывая это для контекста, важно признать, что только то, что расширяется до константного выражения (в соответствующей реализации C), не означает, что значение этого выражения вычисляется во время компиляции. Действительно, учитывая спецификации строгих режимов fp MSVC и Clang, я ожидаю, что эти режимы отключат большую часть, если не все, вычисления выражений FP во время компиляции (или, как минимум, распространение флагов состояния FP, как если бы вычисления выполнялись при запуске). время).
Таким образом, повышая
FE_INVALID
не обязательно является следствием назначения в
f = NAN
. Если (как в стандартной библиотеке Microsoft C) расширяется до выражения, включающего арифметические операции, то в результате вычисления этого выражения должно быть возбуждено исключение, несмотря на то, что результирующее NaN является тихим. По крайней мере, в реализациях, которые заявляют о полном соответствии с IEC 60559, определяя
__STDC_IEC_559__
макрос функционального тестирования.
Поэтому, хотя я и не буду оспаривать
люди могут задаться вопросом: «Как запись в память может привести к возникновению исключений с плавающей запятой?».
, не было представлено убедительных доказательств того, что такая причинно-следственная связь наблюдалась.
Тем не менее ценность, представленная особым внешним видом
NAN
в выражении, которое оценивается, имеет какое-то физическое проявление. Вполне вероятно, что это должно быть в регистре FPU, и сохранение сигнального NaN из регистра FPU в память действительно может вызвать исключение FP на некоторых архитектурах.