pow() приведено к целому, неожиданный результат
У меня есть некоторые проблемы с использованием целочисленного приведения для pow()
функция на языке программирования C. Я использую компилятор Tiny C Compiler (версия 0.9.24 tcc) для платформы Windows. При выполнении следующего кода он выводит неожиданный результат 100, 99
:
#include <stdio.h>
#include <math.h>
int main(void)
{
printf("%d, ", (int) pow(10, 2));
printf("%d", (int) pow(10, 2));
return 0;
}
Тем не менее, на этом онлайн-компиляторе результат, как и ожидалось 100, 100
, Я не знаю, что вызывает такое поведение. Какие-нибудь мысли? Ошибка программирования от меня, ошибка компилятора?
3 ответа
Вы нашли ошибку в tcc. Спасибо за это. Патч только что был добавлен в репозиторий. Он будет включен в следующий выпуск, но это может занять некоторое время. Конечно, вы можете получить исходный код и создать его самостоятельно. Патч здесь
http://repo.or.cz/w/tinycc.git/commitdiff/73faaea227a53e365dd75f1dba7a5071c7b5e541
Некоторое расследование в коде сборки. (OllyDbg)
#include <stdio.h>
#include <math.h>
int main(void)
{
int x1 = (int) pow(10, 2);
int x2 = (int) pow(10, 2);
printf("%d %d", x1, x2);
return 0;
}
Соответствующий раздел сборки:
FLD QWORD PTR DS:[402000] // Loads 2.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
FLD QWORD PTR DS:[402008] // Loads 10.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
CALL <JMP.&msvcrt.pow> // Calls pow API
// Returned value 100.00000000000000000
...
FLDCW WORD PTR DS:[402042] // OH! LOOK AT HERE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
FLD QWORD PTR DS:[402010] // Loads 2.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
FLD QWORD PTR DS:[402018] // Loads 10.0 onto stack
SUB ESP,8
FSTP QWORD PTR SS:[ESP]
CALL <JMP.&msvcrt.pow> // Calls pow API again
// Returned value 99.999999999999999990
Сгенерированный код для двух вызовов одинаков, но выходные данные разные. Я не знаю, почему TCC положил FLDCW
там. Но главная причина двух разных ценностей - эта линия.
Перед этой строкой круговые биты точного управления Mantissa составляют 53 бита (10), но после выполнения этой строки (она загружает управление регистром FPU) она будет установлена на 64 бита (11). С другой стороны, управление округлением является ближайшим, поэтому результатом является 99,9999999999999999990. Прочитайте больше...
Решение:
После использования (int)
бросить float
для int
, вы должны ожидать эту числовую ошибку, потому что это приведение усекает значения от [0, 1) до нуля.
Предположим, что 10 2 составляет 99,99999999999. После этого броска результат 99.
Попробуйте округлить результат до приведения его к целому числу, например:
printf("%d", (int) (floor(pow(10, 2) + 0.5)) );
Кажется, что метод округления может измениться, поэтому требуется инструкция ASM finit
сбросить FPU. В FreeBASIC на Windows я получаю 99.9999
даже с первой попытки, и поэтому я думаю, что для вас после первой попытки это будет последовательным 99.9999
, (Но я на самом деле называю это неопределенным поведением, больше, чем ошибка в среде выполнения C pow()
.)
Так что мой совет не делать конвертацию с округлением вниз. Чтобы избежать таких проблем, используйте, например:
int x1 = (int)(pow(10, 2)+.5);