SSE在应该向上舍入时向下舍入。

9
我正在开发一个应用程序,将范围在-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]);

2
你能否将此简化为一个自包含的示例程序,以演示问题? - Jason R
1
在执行前后保存参数的值可能会很有用。 - CinchBlue
3
这是所有浮点数处理的默认行为,不仅适用于SSE。根据IEEE 754标准,四舍五入到偶数或银行家舍入是默认的舍入模式。原因是在多个数字上应用时,这最小化了舍入误差,而向上取整则保证存在半点误差。 - Panagiotis Kanavos
2个回答

19
虽然SSE舍入模式默认为“四舍五入”,但它不是我们在学校里学到的那个老式的舍入方法,而是稍微现代化一点的变体,称为银行家舍入(也称无偏舍入、收敛舍入、统计学舍入、荷兰舍入、高斯舍入或奇偶舍入),它会将数字舍入到最近的偶数值。从统计学角度来看,这种舍入方法比更传统的方法更好。您将在诸如rint()之类的函数中看到相同的行为,并且它也是IEEE-754的默认舍入模式
另请注意,虽然标准库函数round()使用传统的舍入方法,但SSE指令ROUNDPS (_mm_round_ps)使用银行家舍入。

1
需要注意的是,银行家舍入法不仅适用于SSE,而且是任何浮点数处理的默认方法。 - Panagiotis Kanavos
@PanagiotisKanavos:谢谢 - 我正准备添加有关IEEE-754默认舍入方法的注释。 - Paul R
我能否在SSE/AVX中以传统方式设置舍入? - TStancek
@TStancek:你可以使用LDMXCSR/_mm_setcsr更改舍入模式 - 我不知道可用的任何模式是否会给你所认为的“传统”舍入。 - Paul R

8
这是所有浮点数处理的默认行为,不仅适用于SSE。根据IEEE 754标准,四舍五入到最近偶数或银行家舍入是默认的舍入模式。
使用此模式的原因是,一致地向上(或向下)舍入会导致半点误差,在进行大量操作时会积累。 半点可能会导致一些相当显着的错误-足以成为超人3中的情节点。
然而,四舍五入到最近偶数或奇数会产生正负半点误差,当在许多操作中应用时,这些误差互相消除。
这在SSE操作中也是可取的。 SSE操作通常用于信号处理(音频,图像),工程和统计场景,其中一致的舍入误差将显示为噪声,并需要额外的处理才能消除(如果可能)。 银行家舍入确保消除了这种噪声。

1
我认为一个真实的例子,而不是超人电影更有趣。 - Z boson
2
从维基链接中得知,“一个著名的实例是温哥华证券交易所于1982年建立的新指数。当时该指数被设定为1000.000(精确到三位小数),经过22个月后下降至约520,而股价在这一期间通常是上涨的。问题的原因是该指数每天重新计算数千次,并且总是四舍五入到三位小数,由此积累了舍入误差。使用更好的舍入方式重新计算,在同一期间结束时得到了1098.892的指数值。” - Z boson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接