Странная передача параметров типа float в функции в Windows

Тут меня смущает кусок кода, который запускается в windows!
Вот код:

#define point_float2uint(x) *((unsigned int *)&x)float divide_1000(float y)
{
float v = y / 1000.0f;
return v;
}

float divide_1000(int y)
{
float v = float(y) / 1000.0f;
return v;
}void float_test(void)
{
int num[5] = {67975500, 67251500, 67540620, 69435500, 70171500};
for (int i = 0; i < 5; ++i)
{
int a = num[i];
float af_f = divide_1000(float(a));
float af_i = divide_1000((a));
printf("src num:%d,  af_f:%f, %x, af_i:%f, %x\n", num[i], af_f, point_float2uint(af_f), af_i, point_float2uint(af_i));
}
}

Вот вывод, скомпилированный vs2005:

src num:67975500,  af_f:67975.507813, 4784c3c1, af_i:67975.500000, 4784c3c0
src num:67251500,  af_f:67251.507813, 478359c1, af_i:67251.500000, 478359c0
src num:67540620,  af_f:67540.625000, 4783ea50, af_i:67540.617188, 4783ea4f
src num:69435500,  af_f:69435.507813, 47879dc1, af_i:69435.500000, 47879dc0
src num:70171500,  af_f:70171.507813, 47890dc1, af_i:70171.500000, 47890dc0

Вопрос в том, почему я используюdivide_1000«, получить другой результат в Windows? Это не то, что я хочу!
И я считаю, что не все целочисленные результаты по-разному, но некоторые, как в коде выше.

Вот вывод, скомпилированный gcc4.4.5 в debian:

src num:67975500,  af_f:67975.507812, 4784c3c1, af_i:67975.507812, 4784c3c1
src num:67251500,  af_f:67251.507812, 478359c1, af_i:67251.507812, 478359c1
src num:67540620,  af_f:67540.625000, 4783ea50, af_i:67540.625000, 4783ea50
src num:69435500,  af_f:69435.507812, 47879dc1, af_i:69435.507812, 47879dc1
src num:70171500,  af_f:70171.507812, 47890dc1, af_i:70171.507812, 47890dc1

Я получаю один и тот же результат при использовании другой функцииdivide_1000«. Это то, что я хочу.

1

Решение

Здесь задействовано довольно много настроек генерации кода, которые влияют на результат. Разница, о которой вы сообщаете, заметна в неоптимизированном коде при использовании модели с плавающей запятой по умолчанию (то есть «точной» модели) при использовании «классических» инструкций FPU для вычислений с плавающей запятой.

Компилятор переводит первый вызов буквально: исходное целочисленное значение сначала преобразуется в float — 4-байтовое значение с плавающей точкой — сохраняется в памяти (как аргумент функции). Это преобразование округляет значение до +6.7975504e+7, что уже не точно. Позже что float значение считывается из памяти формы внутри первой функции и используется для дальнейших вычислений.

Второй звонок проходит int значение функции, которая непосредственно загружается в высокоточный регистр FPU и используется для дальнейших вычислений. Даже если вы указали явное преобразование из int в float во второй функции компилятор решил проигнорировать ваш запрос. Это значение никогда не преобразуется буквально в floatЭто означает, что вышеупомянутая потеря точности никогда не происходит.

Это то, что вызывает разницу, которую вы наблюдали.

Если вы переписываете вторую функцию как

float divide_1000(int y)
{
float fy = y;
float v = fy / 1000.0f;
return v;
}

т.е. добавить дополнительный шаг, который сохраняет float значение для именованного места в памяти, компилятор выполнит этот шаг в неоптимизированном коде. Это приведет к тому, что результаты станут идентичными.

Опять же, вышесказанное относится к коду, скомпилированному без оптимизации, когда компилятор обычно пытается перевести все операторы очень близко (но не всегда точно). В оптимизированном коде компилятор исключает «ненужные» промежуточные преобразования в float и все «ненужные» промежуточные хранилища памяти в обоих случаях дают одинаковые результаты.

Возможно, вы также захотите поэкспериментировать с другими моделями с плавающей точкой (то есть «строгим» и «быстрым»), чтобы увидеть, как это влияет на результаты. Эти модели с плавающей точкой существуют специально для решения проблем, подобных той, которую вы наблюдали.

Если вы измените настройки генерации кода компилятора и заставите его использовать инструкции SSE для арифметики с плавающей запятой, результаты также могут измениться (в моем эксперименте разница исчезает, когда вместо инструкций FPU используется набор команд SSE2).

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]