上证所该向上舍入时向下舍入

SSE rounds down when it should round up

我正在开发一个将 -1.0 到 1.0 范围内的 Float 样本转换为有符号 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 舍入模式默认为 "round to nearest",但它并不是我们在学校学到的熟悉的旧舍入方法,而是一种稍微更现代的变体,称为 Banker's rounding (aka unbiased rounding, convergent rounding, statistician's rounding, Dutch rounding, Gaussian rounding or odd–even rounding), which rounds to the nearest even integer value. This rounding method is supposedly better than the more traditional method, from a statistical perspective. You will see the same behaviour with functions such as rint(), and it is also the default rounding mode for IEEE-754

另请注意,标准库函数 round() uses the traditional rounding method, the SSE instruction ROUNDPS (_mm_round_ps) 使用银行四舍五入。

这是 所有 浮点处理的默认行为,而不仅仅是 SSE。 Round half to even or banker's rounding 是根据 IEEE 754 标准的默认舍入模式。

使用它的原因是持续向上(或向下)舍入会导致半点误差,即使在中等数量的操作中应用时也会累积。半点可能会导致一些相当严重的错误——严重到足以成为超人 3 中的情节点。

虽然将一半舍入为偶数或奇数,但会导致负半点误差和正半点误差,当应用于许多操作时,这些误差会相互消除。

这在SSE操作中也是可取的。 SSE 操作通常用于信号处理(音频、图像)、工程和统计场景,在这些场景中,一致的舍入误差会显示为噪声,需要额外处理才能消除(如果可能)。银行家的四舍五入确保消除这种噪音