当它应该向上舍入时,SSE会向下舍入

Geo*_*rey 8 c++ x86 sse rounding-error intrinsics

我正在开发一个应用程序,它将-1.0到1.0范围内的Float样本转换为带符号的16位,以确保优化(SSE)例程的输出是准确的我已编写了一组运行非优化版本的测试SSE版本并比较它们的输出.

在开始之前,我已经确认SSE舍入模式设置为最接近.

在我的测试用例中,公式为:

ratio = 65536 / 2
output = round(input * ratio)
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,结果是准确的,但在一个特定的输入上,我看到输入失败-0.8499908447265625.

-0.8499908447265625 * (65536 / 2) = -27852.5
Run Code Online (Sandbox Code Playgroud)

正常代码正确地将其舍入为-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);
  }
}
Run Code Online (Sandbox Code Playgroud)

自给自足的示例:

/* 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]);
Run Code Online (Sandbox Code Playgroud)

Pau*_*l R 14

尽管SSE舍入模式默认为"舍入到最近",但它并不是我们在学校学到的旧的熟悉的舍入方法,而是一种稍微更现代的变体,称为Banker的舍入(也称为无偏舍入,收敛舍入,统计学舍入,舍入舍入,高斯舍入或奇偶舍入),舍入到最接近的偶数整数值.从统计角度来看,这种舍入方法应该比更传统的方法更好.您将看到与rint()等函数相同的行为,它也是IEEE-754默认舍入模式.

另请注意,虽然标准库函数round()使用传统的舍入方法,但SSE指令ROUNDPS(_mm_round_ps)使用银行家的舍入.


Pan*_*vos 7

这是所有浮点处理的默认行为,而不仅仅是SSE. 根据IEEE 754标准,圆形半边甚至银行家的舍入是默认的舍入模式.

使用它的原因是始终向上舍入(或向下)会导致半点误差,即使在适度数量的操作中应用也会累积.半分可能导致一些非常重要的错误 - 足够重要,它们成为超人3中的情节点.

虽然圆形的一半到偶数或奇数,但是当应用于许多操作时,会产生消极和正半点误差,这些误差会相互消除.

这在SSE操作中也是期望的.SSE操作通常用于信号处理(音频,图像),工程和统计场景,其中一致的舍入误差将表现为噪声并且需要额外的处理来移除(如果可能的话).银行家的舍入确保消除这种噪音

  • 来自维基链接。“一个著名的例子是温哥华证券交易所在 1982 年设立的一个新指数。它最初设定为 1000.000(精确到小数点后三位),22 个月后跌至 520 左右——而在 2017 年,股价普遍上涨。该问题是由于该指数每天重新计算数千次,并且总是向下舍入至小数点后三位,从而导致舍入误差累积。以更好的舍入方式重新计算,最终得出的指数值为 1098.892。同一时期”。 (2认同)