Что здесь происходит в функции pow?

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

В приведенном ниже коде я инициализировал int x = pow(10,2) и int y = pow(10,n) (int n = 2) .

В первом случае, когда я печатаю результат, он показывает 100 а в другом случае это 99 .

Я знаю, что pow возвращает double и он усекается при хранении в int , но я хочу спросить, почему результат получается другим.

code1

 #include #include int main() { int n = 2; int x; int y; x = pow(10,2); //Printing Gives Output 100 y = pow(10,n); //Printing Gives Output 99 printf("%d %d" , x , y); } 

Output : 100 99

Почему выход выходит, чтобы быть другим. ?

Моя версия gcc – 4.9.2

Обновление :

Код 2

 int main() { int n = 2; int x; int y; x = pow(10,2); //Printing Gives Output 100 y = pow(10,n); //Printing Gives Output 99 double k = pow(10,2); double l = pow(10,n); printf("%d %d\n" , x , y); printf("%f %f\n" , k , l); } 

Выход: 100 99
100.000000 100.000000

Обновить 2 Инструкции по сборке для CODE1

Сгенерированные инструкции сборки GCC 4.9.2 с использованием gcc -S -masm=intel :

  .LC1: .ascii "%d %d\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: push ebp mov ebp, esp and esp, -16 sub esp, 48 call ___main mov DWORD PTR [esp+44], 2 mov DWORD PTR [esp+40], 100 //Concerned Line fild DWORD PTR [esp+44] fstp QWORD PTR [esp+8] fld QWORD PTR LC0 fstp QWORD PTR [esp] call _pow //Concerned Line fnstcw WORD PTR [esp+30] movzx eax, WORD PTR [esp+30] mov ah, 12 mov WORD PTR [esp+28], ax fldcw WORD PTR [esp+28] fistp DWORD PTR [esp+36] fldcw WORD PTR [esp+30] mov eax, DWORD PTR [esp+36] mov DWORD PTR [esp+8], eax mov eax, DWORD PTR [esp+40] mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC1 call _printf leave ret .section .rdata,"dr" .align 8 LC0: .long 0 .long 1076101120 .ident "GCC: (tdm-1) 4.9.2" .def _pow; .scl 2; .type 32; .endef .def _printf; .scl 2; .type 32; .endef 

    Я знаю, что pow возвращает double, и он усекается при хранении в int, но я хочу спросить, почему результат получается другим.

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

    Тем не менее, как вы поняли, pow(10, n) привела к значению, подобному 99.99999999999997 , что является приближением с точностью до 15 значащих цифр. И тогда вы сказали ему усечь до наибольшего целого числа меньше, чем это, поэтому он отбросил большинство из них.

    (Кроме того, редко бывает хорошей причиной для преобразования double в int . Обычно вы должны либо форматировать его для отображения с помощью чего-то вроде sprintf("%.0f", x) , который правильно округляет или использует функцию floor , который может обрабатывать числа с плавающей запятой, которые могут быть вне диапазона int . Если ни один из них не соответствует вашей цели, например, в расчетах в валюте или дате, возможно, вы не должны использовать числа с плавающей запятой вообще.)

    Здесь происходят две странные вещи. Во-первых, почему pow(10, n) неточно? 10, 2 и 100 – все они представляются как double . Лучший ответ, который я могу предложить, это то, что в стандартной библиотеке C есть ошибка. (Компилятор и стандартная библиотека, которые, как я полагаю, являются gcc и glibc, разрабатываются в разных расписаниях выпуска и в разных командах. Если pow возвращает неточные результаты, это, вероятно, ошибка в glibc, а не gcc.)

    В комментариях к вашему вопросу amdn обнаружил ошибку glibc , связанную с округлением FP, которая может быть связана, и другие вопросы и ответы, которые более подробно описывают, почему это происходит и как это не является нарушением стандарта C. Ответ chux также затрагивает это. (C не требует реализации IEEE 754 , но даже если это так, pow не требуется использовать правильное округление.) Я все равно буду называть это ошибкой glibc, потому что это нежелательное свойство.

    (Также маловероятно, что FPU вашего процессора ошибочен.)

    Во-вторых, почему pow(10, n) отличается от pow(10, 2) ? Это намного проще. gcc оптимизирует вызовы функций, для которых результат можно вычислить во время компиляции, поэтому pow(10, 2) почти наверняка оптимизируется до 100.0 . Если вы посмотрите на сгенерированный код сборки, вы найдете только один вызов pow .

    В руководстве GCC, раздел 6.59, описывается, какие стандартные библиотечные функции могут обрабатываться таким образом (перейдите по ссылке для полного списка):

    Остальные функции предоставляются для целей оптимизации.

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

    […]

    Функции ISO C90 abort, abs, acos, asin, atan2, atan, calloc, ceil, cosh, cos, exit, exp, fabs, floor, fmod, fprintf, fputs, frexp, fscanf, isalnum, isalpha, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit, tolower, toupper, labs, ldexp, log10, log, malloc, memchr, memcmp, memcpy, memset, modf, pow , printf, putchar, puts, scanf, sinh, sin, snprintf, sprintf, sqrt, sscanf, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr, tanh, tan, vfprintf, vprintf и vsprintf. – в функциях, если не указано значение -fno-builtin (или -fno-builtin-function указана для отдельной функции).

    Таким образом, вы можете отключить это поведение с помощью -fno-builtin-pow .

    Почему выход выходит, чтобы быть другим. ? (в обновленном добавленном коде)

    Мы не знаем, что значения разные.

    При сравнении текстового вывода из int/double обязательно напечатайте double с достаточной точностью, чтобы увидеть, является ли она 100.000000 или всего около 100.000000 или в гексагоне, чтобы устранить все сомнения.

     printf("%d %d\n" , x , y); // printf("%f %f\n" , k , l); // Is it the FP number just less than 100? printf("%.17e %.17e\n" , k , l); // maybe 9.99999999999999858e+01 printf("%a %a\n" , k , l); // maybe 0x1.8ffffffffffff0000p+6 

    Почему выход выходит, чтобы быть другим. ? (в исходном коде)

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

     // Higher quality functions return 100.0 pow(10,2) --> 100.0 // Lower quality and/or faster one may return nearby results pow(10,2) --> 100.0000000000000142... pow(10,2) --> 99.9999999999999857... 

    Присвоение числа с плавающей запятой (FP) int просто снижает долю независимо от того, насколько близко доля равна 1.0

    При преобразовании FP в целое число лучше контролировать преобразование и округлять, чтобы справиться с небольшими вычислительными различиями.

     // long int lround(double x); long i = lround(pow(10.0,2.0)); 

    Вы не первый, кто это нашел. Вот форма обсуждения 2013: pow (), отличная от целого, неожиданный результат

    Я предполагаю, что код сборки, созданный ребятами tcc, вызывает второе округление значения после вычисления результата, который ДЕЙСТВИТЕЛЬНО близок к 100. Как и mikijov в этом историческом сообщении, похоже, что ошибка исправлена.

    Как отмечали другие, Code 2 возвращает 99 из-за усечения с плавающей запятой. Причина, по которой Code 1 возвращает другой и правильный ответ, связана с оптимизацией libc.

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

    Вы обманули его, думая, что входы реальны, и поэтому он дает приблизительный ответ, который, как оказалось, немного ниже 100, например 99.999999, который затем усекается до 99.

    Давайте будем гением компьютера.