Assert (!isnan(x)) завершается успешно, но printf("%f",x) показывает -nan

Я использую gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0. Убунту 24.04

В моем коде есть ошибка, из-за которой функция числового анализа дает очень разные ответы. Каждое из следующих значений возвращает true. х — двойной.

assert (fpclassify(x) == FP_NORMAL);;
assert (x == x);
assert (!(isnan(x)));
assert (isnormal(x));

uint64_t abc = *((long long*)&x) ;
printf("%" PRIx64 "\n", abc);
/* Prints fff8000000000000 */
printf("%f",x);
/* Prints -nan */
uint64_t x4 = 0xfff8000000000000;
assert (x4 == abc);

Я вызываю код C из OCaml, поэтому возможно, что среда выполнения OCaml искажает значения.

Может ли это быть ошибка компилятора или есть другие объяснения?

Обновлено: в предыдущей версии этого поста вместо логического отрицания ошибочно использовалось побитовое отрицание.

Обратите внимание, что оба isnan и isnormal возвращают ненулевое целое значение, если предикат имеет значение «истина». Это означает, что побитовый оператор ~ может не выдать вам ноль. Если вам нужно логическое дополнение, используйте !.

Some programmer dude 23.05.2024 07:43

Не используйте побитовое дополнение (~) для isnan и isnormal. Используйте логическое дополнение (!). Побитовое дополнение, вероятно, всегда будет возвращать для них логическое истинное значение (например, ~1 — это -2, а не 0, как вы думаете).

Tom Karzes 23.05.2024 08:00

Когда вы столкнетесь с чем-то подобным, не удивляйтесь результату и не публикуйте сообщение в Stack Overflow. Разберите проблему на части. Распечатайте каждый отдельный результат напрямую, например printf("%d/n", isnormal(x)); и printf("%d/n", ~isnormal(x));, чтобы увидеть, что происходит. И поищите документацию по функциям и операторам, с которыми вы работаете.

Eric Postpischil 23.05.2024 08:55

@EricPostpischil Спасибо, я исправил сообщение, заменив побитовое отрицание логическим отрицанием и включив значение, напечатанное printf.

Patrick Nicodemus 24.05.2024 02:12

Вы уверены, что у вас нет определения NDEBUG, чтобы утверждения действительно что-то делали? Можете ли вы создать минимальный воспроизводимый пример, который можно скомпилировать и запустить для воспроизведения проблемы?

Nate Eldredge 24.05.2024 02:21

@NateEldredge Когда я редактирую любую строку, чтобы подтвердить отрицание, программа останавливается с сообщением «Утверждение не удалось:». В частности, любое из утверждений assert (x !=x); assert (fpclassify(x) != FP_NORMAL), assert ((isnan(x))); assert (!isnormal(x)); вызывает «Утверждение не выполнено, прервано (сброс ядра)».

Patrick Nicodemus 24.05.2024 02:27

@PatrickNicodemus Мы просим предоставить автономный фрагмент кода, который другие смогут скомпилировать и запустить без изменений и увидеть тот же результат, что и вы.

dbush 24.05.2024 02:35

Также укажите флаги компилятора, версию библиотеки и ОС и т. д. В частности, если вы используете -ffast-math или -ffinite-math-only или -Ofast, вы сообщаете компилятору, что значения NaN никогда не возникают, а затем isnan(x) оптимизируется до константы 0, поэтому можно ожидать противоречивых результатов, если у вас есть NaN.

Nate Eldredge 24.05.2024 02:39

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

Nate Eldredge 24.05.2024 05:45

Нейт, я понимаю твое разочарование. Я занимаюсь сбором информации, которую вы просили. Я ищу ошибку в большом проекте программного обеспечения с открытым исходным кодом. Флаги компилятора по умолчанию, используемые инструментом сборки, находятся здесь. github.com/owlbarn/owl/blob/…

Patrick Nicodemus 24.05.2024 05:48

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

Patrick Nicodemus 24.05.2024 05:51

@PatrickNicodemus -ffast-math за код, в котором говорится: «Наша миссия — расширить границы высокопроизводительных научных вычислений, предоставить исследователям и отраслевым программистам мощную основу для написания краткого, быстрого и безопасного аналитического кода».?!?! Ооооо. См. Можно ли безопасно использовать -ffast-math в типичном проекте? TLDR: Нет.

Andrew Henle 24.05.2024 05:53

Спасибо за ссылку, Андрей, выглядит тревожно. @NateEldredge Тем временем, пока я пытаюсь свести к минимуму ошибку, лучшее, что я могу сделать, это направить вас к созданной мной проблеме на GitHub, документируя мой прогресс в работе над ошибкой. github.com/owlbarn/owl/issues/665

Patrick Nicodemus 24.05.2024 05:59

Хорошо спасибо. В будущем, если у вас будет доступна только часть запрошенной информации, вы можете включить в свое редактирование что-то вроде «Я все еще пытаюсь узнать X», чтобы люди знали, что их не игнорируют.

Nate Eldredge 24.05.2024 06:01

@PatrickNicodemus «Редактировать: в предыдущей версии этого поста по ошибке использовалось побитовое отрицание вместо логического отрицания» --> это плохой этикет, когда приходят ответы, и оказывать медвежью услугу этому и тому. Лучше оставить код таким, какой он был, или, возможно, добавить обновление.

chux - Reinstate Monica 28.05.2024 23:41
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
15
118
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Оператор ~ является побитовым дополнением — он переворачивает все биты значения — это нелогично. Поэтому применение его к «логическому» значению редко будет давать то, что вы хотите. Например, программа:

#include <stdbool.h>
#include <stdio.h>

int main() {
    if (~true) {
        printf("~true is true\n");
    }
}

распечатаю ~true is true

Большое спасибо. Сообщение было обновлено, чтобы устранить эту ошибку.

Patrick Nicodemus 24.05.2024 02:16

Каждый из макросов классификации с плавающей запятой issomething «возвращает ненулевое значение тогда и только тогда, когда его аргумент» находится в тестируемой категории (подпункты C 2018 в 7.12.3). Таким образом, он не обязательно выдает 0 или 1. Он может выдавать 2, 4 или другие числа.

Когда вы применяете к ним оператор побитового дополнения ~, нулевое значение даст ненулевой результат, но 1, 2, 4 и большинство других значений также дадут ненулевой результат; в результате биты, которые были включены в операнде, будут отключены, а биты, которые были выключены, включены, что обычно дает ненулевое число.

При дополнении до двух только значение, равное ~0, −1, даст нулевой результат.

Чтобы проверить обратное isnormal, вы можете использовать логическое отрицание: !isnormal(x).

Большое спасибо. Сообщение было обновлено, чтобы устранить эту ошибку.

Patrick Nicodemus 24.05.2024 02:16
Ответ принят как подходящий

Судя по вашим комментариям, ваш код создается с помощью -Ofast -ffast-math. Из руководства gcc они включают -ffinite-math-only, поведение которого следующее:

Разрешить оптимизацию для арифметики с плавающей запятой, которая предполагает, что аргументы и результаты не являются NaN или +-Inf.

Компилятор не глючный; он ведет себя так, как задокументировано. Но вы нарушили свой контракт с компилятором, пообещав, что NaN никогда не появится, а затем все равно его применив.

В частности, среди рассматриваемых оптимизаций есть то, что isnan(x) будет оптимизировано до константы 0, так что кажется, что она возвращает false, даже если x действительно является NaN; x == x будет оптимизировано до константы 1; и isnormal и fpclassify не будут проверять NaN. Отсюда и противоречивые результаты, которые вы наблюдаете.

Попробуйте перестроить без этих опций (-O3 безопасно), и вы увидите, что утверждения не работают.

Отключение -Ofast -ffast-math заставило код вести себя ожидаемым образом. Также была решена исходная ошибка, когда функция возвращала -nan вместо ожидаемого. Я написал это по связанной проблеме с GitHub и предложил удалить -Ofast и -ffast-math из флагов сборки по умолчанию.

Patrick Nicodemus 24.05.2024 22:58

Кстати, clang предупредит об использовании isnan() и isinf(), когда -ffast-math действует.

Nate Eldredge 24.05.2024 23:23

Другие вопросы по теме