SSE округляется вниз, когда должно округляться

Я работаю над приложением, которое преобразует образцы с плавающей запятой в диапазоне от -1,0 до 1,0 в знаковый 16-битный, чтобы гарантировать точность вывода оптимизированных (SSE) подпрограмм. Я написал набор тестов, который запускает неоптимизированную версию для версия SSE и сравнивает их вывод.

Перед тем, как начать, я подтвердил, что режим округления SSE установлен на ближайший.

В моем тестовом случае формула имеет вид:

ratio = 65536 / 2
output = round(input * ratio)

По большей части результаты точны, но на одном конкретном входе я вижу сбой для ввода -0.8499908447265625,

-0.8499908447265625 * (65536 / 2) = -27852.5

Нормальный код правильно округляет это до -27853, но код SSE округляет это до -27852,

Вот код SSE в использовании:

void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
static float ratio = 65536.0f / 2.0f;
static __m128 mul  = _mm_set_ps1(ratio);

for(unsigned int i = 0; i < samples; i += 4, in += 4, out += 4)
{
__m128  xin;
__m128i con;

xin = _mm_load_ps(in);
xin = _mm_mul_ps(xin, mul);
con = _mm_cvtps_epi32(xin);

out[0] = _mm_extract_epi16(con, 0);
out[1] = _mm_extract_epi16(con, 2);
out[2] = _mm_extract_epi16(con, 4);
out[3] = _mm_extract_epi16(con, 6);
}
}

Автономный пример по запросу:

/* standard math */
float   ratio  = 65536.0f / 2.0f;
float   in [4] = {-1.0, -0.8499908447265625, 0.0, 1.0};
int16_t out[4];
for(int i = 0; i < 4; ++i)
out[i] = round(in[i] * ratio);

/* sse math */
static __m128 mul  = _mm_set_ps1(ratio);
__m128  xin;
__m128i con;

xin = _mm_load_ps(in);
xin = _mm_mul_ps(xin, mul);
con = _mm_cvtps_epi32(xin);

int16_t outSSE[4];
outSSE[0] = _mm_extract_epi16(con, 0);
outSSE[1] = _mm_extract_epi16(con, 2);
outSSE[2] = _mm_extract_epi16(con, 4);
outSSE[3] = _mm_extract_epi16(con, 6);

printf("Standard = %d, SSE = %d\n", out[1], outSSE[1]);

8

Решение

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

Обратите внимание, что тогда как стандартная функция библиотеки круглый() использует традиционный метод округления, инструкцию SSE ROUNDPS (_mm_round_ps) использует банковское округление.

13

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

Это поведение по умолчанию для все обработка с плавающей запятой, а не только SSE. Округление от половины до четного или округление банкира является режимом округления по умолчанию в соответствии со стандартом IEEE 754.

Причина, по которой это используется, заключается в том, что последовательное округление в большую или меньшую сторону приводит к ошибке в половину точки, которая накапливается при применении даже при умеренном количестве операций. Половины могут привести к некоторым довольно существенным ошибкам — достаточно значительным, чтобы они стали точкой сюжета в Superman 3.

Однако округление от половины до четного или нечетного приводит к ошибкам как отрицательного, так и положительного полупункта, которые устраняют друг друга при применении ко многим операциям.

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

7

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