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);

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