Я использую 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
. Используйте логическое дополнение (!
). Побитовое дополнение, вероятно, всегда будет возвращать для них логическое истинное значение (например, ~1
— это -2
, а не 0
, как вы думаете).
Когда вы столкнетесь с чем-то подобным, не удивляйтесь результату и не публикуйте сообщение в Stack Overflow. Разберите проблему на части. Распечатайте каждый отдельный результат напрямую, например printf("%d/n", isnormal(x));
и printf("%d/n", ~isnormal(x));
, чтобы увидеть, что происходит. И поищите документацию по функциям и операторам, с которыми вы работаете.
@EricPostpischil Спасибо, я исправил сообщение, заменив побитовое отрицание логическим отрицанием и включив значение, напечатанное printf.
Вы уверены, что у вас нет определения NDEBUG
, чтобы утверждения действительно что-то делали? Можете ли вы создать минимальный воспроизводимый пример, который можно скомпилировать и запустить для воспроизведения проблемы?
@NateEldredge Когда я редактирую любую строку, чтобы подтвердить отрицание, программа останавливается с сообщением «Утверждение не удалось:». В частности, любое из утверждений assert (x !=x); assert (fpclassify(x) != FP_NORMAL), assert ((isnan(x))); assert (!isnormal(x));
вызывает «Утверждение не выполнено, прервано (сброс ядра)».
@PatrickNicodemus Мы просим предоставить автономный фрагмент кода, который другие смогут скомпилировать и запустить без изменений и увидеть тот же результат, что и вы.
Также укажите флаги компилятора, версию библиотеки и ОС и т. д. В частности, если вы используете -ffast-math
или -ffinite-math-only
или -Ofast
, вы сообщаете компилятору, что значения NaN никогда не возникают, а затем isnan(x)
оптимизируется до константы 0, поэтому можно ожидать противоречивых результатов, если у вас есть NaN.
Еще раз, флаги компилятора, пожалуйста? И минимально воспроизводимый пример. Очень неприятно пытаться помочь кому-то, когда каждый раз, когда вы запрашиваете дополнительную информацию, вам предоставляют только половину.
Нейт, я понимаю твое разочарование. Я занимаюсь сбором информации, которую вы просили. Я ищу ошибку в большом проекте программного обеспечения с открытым исходным кодом. Флаги компилятора по умолчанию, используемые инструментом сборки, находятся здесь. github.com/owlbarn/owl/blob/…
На самом деле, вчера вечером я потратил несколько часов, извлекая ошибочный код из контекста более крупного проекта и превращая его в отдельный исполняемый файл. К сожалению, когда я вытащил код из исходного контекста, ошибка исчезла, и минимизация оказалась бесполезной.
@PatrickNicodemus -ffast-math
за код, в котором говорится: «Наша миссия — расширить границы высокопроизводительных научных вычислений, предоставить исследователям и отраслевым программистам мощную основу для написания краткого, быстрого и безопасного аналитического кода».?!?! Ооооо. См. Можно ли безопасно использовать -ffast-math в типичном проекте? TLDR: Нет.
Спасибо за ссылку, Андрей, выглядит тревожно. @NateEldredge Тем временем, пока я пытаюсь свести к минимуму ошибку, лучшее, что я могу сделать, это направить вас к созданной мной проблеме на GitHub, документируя мой прогресс в работе над ошибкой. github.com/owlbarn/owl/issues/665
Хорошо спасибо. В будущем, если у вас будет доступна только часть запрошенной информации, вы можете включить в свое редактирование что-то вроде «Я все еще пытаюсь узнать X», чтобы люди знали, что их не игнорируют.
@PatrickNicodemus «Редактировать: в предыдущей версии этого поста по ошибке использовалось побитовое отрицание вместо логического отрицания» --> это плохой этикет, когда приходят ответы, и оказывать медвежью услугу этому и тому. Лучше оставить код таким, какой он был, или, возможно, добавить обновление.
Оператор ~
является побитовым дополнением — он переворачивает все биты значения — это нелогично. Поэтому применение его к «логическому» значению редко будет давать то, что вы хотите. Например, программа:
#include <stdbool.h>
#include <stdio.h>
int main() {
if (~true) {
printf("~true is true\n");
}
}
распечатаю ~true is true
Большое спасибо. Сообщение было обновлено, чтобы устранить эту ошибку.
Каждый из макросов классификации с плавающей запятой issomething
«возвращает ненулевое значение тогда и только тогда, когда его аргумент» находится в тестируемой категории (подпункты C 2018 в 7.12.3). Таким образом, он не обязательно выдает 0 или 1. Он может выдавать 2, 4 или другие числа.
Когда вы применяете к ним оператор побитового дополнения ~
, нулевое значение даст ненулевой результат, но 1, 2, 4 и большинство других значений также дадут ненулевой результат; в результате биты, которые были включены в операнде, будут отключены, а биты, которые были выключены, включены, что обычно дает ненулевое число.
При дополнении до двух только значение, равное ~0
, −1, даст нулевой результат.
Чтобы проверить обратное isnormal
, вы можете использовать логическое отрицание: !isnormal(x)
.
Большое спасибо. Сообщение было обновлено, чтобы устранить эту ошибку.
Судя по вашим комментариям, ваш код создается с помощью -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 из флагов сборки по умолчанию.
Кстати, clang предупредит об использовании isnan()
и isinf()
, когда -ffast-math
действует.
Обратите внимание, что оба isnan и isnormal возвращают ненулевое целое значение, если предикат имеет значение «истина». Это означает, что побитовый оператор
~
может не выдать вам ноль. Если вам нужно логическое дополнение, используйте!
.