【问题标题】:SSE rounds down when it should round upSSE 在应该向上取整时向下取整
【发布时间】:2016-01-11 22:37:36
【问题描述】:

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

【问题讨论】:

  • 你能把它简化为一个独立的示例程序来演示这个问题吗?
  • 在执行前后保存参数的值可能很有用。
  • 这是 all 浮点处理的默认行为,而不仅仅是 SSE。 Round half to even or banker's rounding 是根据 IEEE 754 标准的默认舍入模式。原因是当应用于多个数字时,这可以最大限度地减少舍入误差,而舍入保证存在半点误差。

标签: c++ x86 sse intrinsics rounding-error


【解决方案1】:

虽然 SSE 舍入模式默认为“四舍五入”,但它并不是我们在学校都学过的旧的熟悉的舍入方法,而是一种更现代的变体,称为 Banker's rounding(又名无偏舍入、收敛舍入、统计学家的舍入、荷兰式舍入、高斯舍入或奇偶舍入),舍入到最接近的偶数整数值。从统计的角度来看,这种舍入方法应该比更传统的方法更好。您将看到与 rint() 等函数相同的行为,它也是 default rounding mode for IEEE-754

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

【讨论】:

  • 需要注意的是,银行家的四舍五入是任何浮点处理的默认值,而不仅仅是SSE
  • @PanagiotisKanavos:谢谢 - 我只是在添加关于 IEEE-754 的默认舍入方法的注释。
  • 我能否以某种方式在 SSE/AVX 中将舍入设置为传统方式?
  • @TStancek:您可以使用 LDMXCSR/_mm_setcsr 更改舍入模式 - 我不知道是否有任何可用模式会为您提供您认为的“传统”舍入。
【解决方案2】:

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

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

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

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

【讨论】:

  • 我觉得一个真实的例子,而不是超人电影更有趣。
  • 来自维基链接。 “一个著名的例子是 1982 年温哥华证券交易所设立的一个新指数。最初设定为 1000.000(精确到小数点后三位),22 个月后跌至 520 左右——而股票价格普遍上涨问题是由于指数每天要重新计算数千次,并且总是四舍五入到小数点后3位,这样舍入误差就会累积。用更好的舍入重新计算得出的指数值在结束时为1098.892同一时期。”
猜你喜欢
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-06
  • 2016-09-02
  • 2022-01-24
  • 1970-01-01
相关资源
最近更新 更多