Тот же простой расчет, разные результаты

Используя gcc 4.8.2 (Ubuntu 14.04), я получаю разные результаты, в основном вычисляя значение одинаковым образом. Также есть разница в зависимости от архитектуры (32 бит / 64 бит) в тестируемых системах.

#include <math.h>
#include <stdio.h>

int main()
{
    float h = 0.11f; 
    float y = 0.11f;
    float g = 1.37906f;
    float x = 2.916949f;

    float result1 = (h * y / fabs(g)) / x;

    float result2 = h * y / fabs(g);
    result2 /= x;

    float result3 = (h * y / g) / x;

    printf("%.20f \n", result1); //0.00300796888768672943 
    printf("%.20f \n", result2); //0.00300796912051737309 
    printf("%.20f \n", result3); //0.00300796912051737309 on x64
                                 //0.00300796888768672943 on x32 
}

В чем причина этого и как я могу предвидеть или избежать этих различий?

Изменить: приведение Fabs к плаванию не меняет результаты, по крайней мере, в моей системе (см. Комментарии Оли Чарльзуорт).

3 ответа

Решение

Вместо того, чтобы заставлять все компиляторы C реализовывать строгий стандарт для вычислений с плавающей запятой, как это делали ранние стандарты Java, стандарт C99 допускает некоторые вариации относительно идеального режима, когда каждая операция выполняется в порядке и округляется в соответствии с форматом IEEE 754, соответствующим к типу с плавающей точкой.

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

  1. Если вы собираетесь сгенерировать код 387, скомпилируйте его с последним GCC (4.8 должно подойти) и -std=c99, Без этого (конкретно без -fexcess-precision=standard что это подразумевает), точный результат вычислений с плавающей точкой непредсказуем, и вы позволяете компилятору производить разные результаты для result1, result2 а также result3). С -std=c99, значения result1 а также result3 должен быть одинаковым. Значение result2 может быть другим, потому что промежуточное назначение result2 заставляет значение в этой точке вычисления быть округленным до float,
  2. Прекратите генерировать код 387, вместо этого создайте код SSE2 (опции -msse2 -mfpmath=sse). В этом режиме все три вычисления, в которых fabs был заменен fabsf должен дать тот же результат. Это имеет недостаток генерации кода, который совместим только с процессорами, произведенными за последние 12 лет или около того (!)

Дополнительная информация: post1, post2, написанные с точки зрения человека, который намеревается написать статический анализатор для программ на C, который точно предсказывает результаты вычислений с плавающей запятой.

Первые два отличаются, потому что fabs возвращает double, Таким образом, в первом варианте деление на x сделано с двойной точностью. Во второй версии это сделано с одинарной точностью.

Причина в том, что вы используете float тип с точностью около 6 десятичных знаков.
И результаты согласуются в пределах первых 6 значащих цифр.

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