На ошибке округления поплавка - PullRequest
5 голосов
/ 22 июня 2011

Я не понимаю вывод следующей программы:

int main()
{
    float  x     = 14.567729f;
    float  sqr   = x * x;
    float  diff1 = sqr - x * x;
    double diff2 = double(sqr) - double(x) * double(x);
    std::cout << diff1 << std::endl;
    std::cout << diff2 << std::endl;
    return 0;
}

Выход:

6.63225e-006
6.63225e-006

Я использую VS2010, x86 компилятор.

Я ожидаю получить другой вывод

0
6.63225e-006

Почему diff1 не равно 0? Для расчета sqr - x * x компилятор увеличивает точность с плавающей запятой в два раза. Почему?

Ответы [ 4 ]

3 голосов
/ 22 июня 2011

Регистры с плавающей запятой имеют размер 80 бит (на большинстве современных процессоров)

Во время выражения результат является 80-битным значением. Он усекается только до 32 (с плавающей запятой) или 64 (с двойной), когда ему назначается место в памяти. Если вы храните все в регистрах (попробуйте скомпилировать с -O3), вы можете увидеть другой результат.

Составлено с: -03:

> ./a.out
0
6.63225e-06
2 голосов
/ 22 июня 2011
float  diff1 = sqr - x * x;
double diff2 = double(sqr) - double(x) * double(x);

Почему diff1 не равно 0?

Поскольку вы уже кэшировали sqr = x*x и принудительно представили его как float.

Для вычисления sqr - x * x компилятор увеличивает точность с плавающей запятой в два раза.Почему?

Потому что именно так C делал вещи еще до того, как появился стандарт C.Я не думаю, что современные компиляторы связаны с этим соглашением, но многие все еще следуют ему.В этом случае правые части вычислений diff1 и diff2 будут идентичны.Единственное отличие состоит в том, что после вычисления правой части float diff1 = ... двойной результат преобразуется обратно в число с плавающей точкой.

0 голосов
/ 22 июня 2011

В архитектурах x86 / x64 для компиляторов характерно преобразование всех 32-разрядных чисел с плавающей запятой в 64-разрядные двойные числа для вычислений; проверьте выходную сборку, чтобы увидеть, дают ли два варианта одинаковые инструкции. Единственная разница между типами - это хранилище .

0 голосов
/ 22 июня 2011

Очевидно, стандарт позволяет автоматически увеличивать числа с плавающей запятой в таких выражениях. Смотрите здесь

Найдите на этой странице слово "автоматически повышен" и отметьте первый абзац с этой фразой.

Если мы пойдем по этому абзацу, насколько я понимаю, ваш sqr = x * x первоначально обрабатывается так же, как если бы он был также удвоенным, но после сохранения он округляется до числа с плавающей точкой. Затем, в вашем diff1 = sqr-x * x, x * x снова обрабатывается как double, как и sqr, хотя он уже округлен. Следовательно, он дает тот же результат, что и приведение их всех к двойным значениям: тогда sqr является двойным, но уже округлен до точности с плавающей запятой, и снова x * x с двойной точностью.

...