Тот же простой расчет, разные результаты
Используя 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, какой модели вычисления с плавающей запятой она следует, и можете использовать параметры командной строки, чтобы изменить ее поведение и сделать ее более предсказуемой. Есть два случая:
- Если вы собираетесь сгенерировать код 387, скомпилируйте его с последним GCC (4.8 должно подойти) и
-std=c99
, Без этого (конкретно без-fexcess-precision=standard
что это подразумевает), точный результат вычислений с плавающей точкой непредсказуем, и вы позволяете компилятору производить разные результаты дляresult1
,result2
а такжеresult3
). С-std=c99
, значенияresult1
а такжеresult3
должен быть одинаковым. Значениеresult2
может быть другим, потому что промежуточное назначениеresult2
заставляет значение в этой точке вычисления быть округленным доfloat
, - Прекратите генерировать код 387, вместо этого создайте код SSE2 (опции
-msse2 -mfpmath=sse
). В этом режиме все три вычисления, в которыхfabs
был замененfabsf
должен дать тот же результат. Это имеет недостаток генерации кода, который совместим только с процессорами, произведенными за последние 12 лет или около того (!)
Дополнительная информация: post1, post2, написанные с точки зрения человека, который намеревается написать статический анализатор для программ на C, который точно предсказывает результаты вычислений с плавающей запятой.
Первые два отличаются, потому что fabs
возвращает double
, Таким образом, в первом варианте деление на x
сделано с двойной точностью. Во второй версии это сделано с одинарной точностью.
Причина в том, что вы используете float
тип с точностью около 6 десятичных знаков.
И результаты согласуются в пределах первых 6 значащих цифр.