Почему слишком большие значения, выводимые из pow(), не приводятся таким же образом, если экспонента является переменной?

Я столкнулся с этой необычной ошибкой, работая над некоторыми побитовыми упражнениями. Когда вывод pow() был приведен к unsigned int, результат pow(), вызванный с переменной в качестве показателя степени, стал нулевым, а результат, когда показатель степени был буквальным целым числом, был принудительно обычно равно 0xFFFFFFFF (2^32 - 1). Это происходит только в том случае, если значение чрезмерно велико, в данном случае 2^32. Тип переменной, используемой в качестве аргумента экспоненты, похоже, не влияет на этот результат. Я также попытался сохранить вывод обоих вызовов pow() как удвоение, а затем применить принуждение при ссылке на переменные; диспропорция сохранялась.

#import <math.h>

int main (void) {
  int thirtytwo = 32; // double, unsigned, etc... all yielded the same result

  printf("Raw Doubles Equal: %s\n", pow(2, 32) == pow(2, thirtytwo) ? "true" : "false"); // -> true
  printf("Coerced to Unsigned Equal: %s\n", (unsigned) pow(2, 32) == (unsigned) pow(2, thirtytwo) ? "true": "false"); // -> false

  return 0;
}

Из любопытства я пропустил тот же код через clang/llvm и получил другой результат: независимо от того, был ли показатель степени переменной, приведение результата к беззнаковому целому дало ноль (как и ожидалось).

Изменить: максимальное 32-битное целое число без знака равно 2^32 - 1, поэтому принудительный вывод не является правильным. Моя ошибка заключалась в превышении ограничения на целочисленный размер. Почему gcc по существу округляется до максимального целочисленного значения, является интересным любопытством, но не имеет особого значения.


person Alex Patch    schedule 28.03.2017    source источник
comment
Если беззнаковые целые числа имеют длину 32 бита, как, по вашему мнению, они будут представлять 2 ^ 32? Возможно, засветив несуществующий 33-й бит? Вы переполняете беззнаковое целое и получаете 0.   -  person StoryTeller - Unslander Monica    schedule 28.03.2017
comment
Это не объясняет непоследовательность. Переполнение не происходит, когда показатель степени представляет собой буквальное целое число, например. (unsigned) pow(2, 32) == 0xFFFFFFFF.   -  person Alex Patch    schedule 28.03.2017
comment
Для одного 0xFFFFFFFF == 2^32 -1, а не 2^32. И компилятор может оптимизировать, как он хочет, когда вы делаете вызов с константами, а не с переменными. Разные компиляторы могут даже делать разные вещи.   -  person StoryTeller - Unslander Monica    schedule 28.03.2017
comment
чтобы получить степень двойки, используйте вместо нее 1 << exp   -  person phuclv    schedule 28.03.2017


Ответы (1)


Компилятор будет использовать свертывание констант, чтобы заменить pow(2, 32) постоянным результатом; pow(2, thirtytwo) будет рассчитываться во время выполнения. C11 фактически позволяет вычислениям времени компиляции быть более точными, чем соответствующие вычисления времени выполнения (C11 6.6p5):

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

Например, GCC, как известно, делает это. Таким образом, стандарт C на самом деле не требует, чтобы первый печатал true (и на практике это происходит в некоторых реализациях).


Что касается того, почему второй печатает false: это потому, что pow(2 ^ 32) не может быть представлено в 32-битном целом без знака. C11 6.3.1.4:

Когда конечное значение вещественного типа с плавающей запятой преобразуется в целочисленный тип, отличный от _Bool, дробная часть отбрасывается (т. е. значение усекается до нуля). Если значение целочисленной части не может быть представлено целочисленным типом, поведение не определено.

Таким образом, второй печатает false из-за неопределенного поведения. В отличие от сужения целочисленных преобразований в целые числа без знака, переполнение явно не определено для преобразований из чисел с плавающей запятой в четные целые числа без знака.

Примечательно, что я не могу заставить свой GCC 6.2.0 предупреждать о неопределенном поведении во время компиляции для (unsigned int)pow(2, 32). (Я пробовал с -lm -Wall -Werror -pedantic -ubsan -Wfloat-conversion -Wconversion -Wextra -std=c11 и ничего не выводится).

person Antti Haapala    schedule 28.03.2017
comment
В таком случае, не находите ли вы удивительным, что результат времени выполнения оказывается более точным? У меня под рукой нет машины, только андроид, но интересно, как компилируется (unsigned) pow(2, 32). - person rici; 28.03.2017
comment
@rici - лично я нет. Если GCC хочет оптимизировать время сборки, он может сделать это за счет точности свертывания констант с плавающей запятой, среди прочих способов, конечно. - person StoryTeller - Unslander Monica; 28.03.2017
comment
@рассказчик: я согласен, что результат соответствует стандарту, и вы можете не удивляться; Я намеренно не ответил на ваш комментарий к ОП. Но этот ответ говорит, что время компиляции является более точным, поэтому я спросил Антти. - person rici; 28.03.2017
comment
@rici ответил, что один, второй печатает false из-за UB. При преобразовании типа с плавающей запятой в беззнаковое целое не используется модульная арифметика. - person Antti Haapala; 28.03.2017
comment
Поскольку UB происходит во время компиляции, это все, что нужно (UB позволяет результату первого сравнения быть любым). Однако я согласен с @rici в том, что компилятору было бы разумно предупредить - он предупреждает о других подобных ошибках, так почему бы не предупредить здесь? - person skyking; 28.03.2017
comment
@antti, ах, ты прав. Я неправильно запомнил этот пункт. Это все еще кажется нарушением POLA - person rici; 28.03.2017
comment
@skyking, возможно, потому, что сворачивание выполняется после проверок. Так что не обнаружит. - person Kami Kaze; 28.03.2017