Я работаю над приложением, которое преобразует образцы с плавающей запятой в диапазоне от -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]);
Хотя режим округления SSE по умолчанию установлен на «округление до ближайшего», это не старый знакомый метод округления, который мы все изучали в школе, а немного более современный вариант, известный как Банковское округление (aka непредвзятое округление, сходящееся округление, округление статистики, округление Голландии, округление Гаусса или округление нечетно-четного), которое округляется до ближайшего четного целого значения. Этот метод округления предположительно лучше, чем более традиционный, с точки зрения статистики. Вы увидите то же поведение с такими функциями, как ечать (), и это также режим округления по умолчанию для IEEE-754.
Обратите внимание, что тогда как стандартная функция библиотеки круглый() использует традиционный метод округления, инструкцию SSE ROUNDPS
(_mm_round_ps
) использует банковское округление.
Это поведение по умолчанию для все обработка с плавающей запятой, а не только SSE. Округление от половины до четного или округление банкира является режимом округления по умолчанию в соответствии со стандартом IEEE 754.
Причина, по которой это используется, заключается в том, что последовательное округление в большую или меньшую сторону приводит к ошибке в половину точки, которая накапливается при применении даже при умеренном количестве операций. Половины могут привести к некоторым довольно существенным ошибкам — достаточно значительным, чтобы они стали точкой сюжета в Superman 3.
Однако округление от половины до четного или нечетного приводит к ошибкам как отрицательного, так и положительного полупункта, которые устраняют друг друга при применении ко многим операциям.
Это также желательно в операциях SSE. Операции SSE обычно используются в обработке сигналов (аудио, изображения), инженерных и статистических сценариях, где постоянная ошибка округления может выглядеть как шум и требует дополнительной обработки для устранения (если возможно). Округление банкира гарантирует, что этот шум устранен