【问题标题】:x86 Assembly (SSE): Unexpected Multiplication Resultx86 程序集 (SSE):意外的乘法结果
【发布时间】:2016-02-16 22:52:44
【问题描述】:

以下代码应将 (单精度)浮点数量化为 32 位整数。 由于正数范围仅包含2^31 - 1(离散)级别,代码将样本乘以该值,并将结果四舍五入为整数:

mov eax, 0x7FFFFFFF   // eax = 2^31 - 1
cvtsi2ss xmm1, eax    // convert eax to float --> xmm1
movss xmm0, [sample]  // where 'sample' is of type float
mulss xmm0, xmm1      // Get sample's quantum into xmm0
cvtss2si eax, xmm0    // Round quantum to the nearest integer --> eax

问题是:对于sample1.0f,最终结果(eax 值)是 0x80000000 = 2^31,超出范围。 预期结果为1.0 x (2^31 - 1) = (2^31 - 1) = 0x7FFFFFFF

而且,这个值实际上是 -2^31 的 2 的补码表示(注意减号)。

我在这里错过了什么?

{ 正在使用 MSVC2010 进行测试。 } `

【问题讨论】:

  • 0x7FFFFFFF 转换为浮点数时舍入到 2147483648 开头
  • 是的,你应该试试双精度。
  • @harold,所以基本上,你是说xmm1 拥有2^31 的浮点表示,而not2^31 - 1
  • 单精度是 32 位,尾数只有 24 位,所以是的,你不能指望能够在不损失精度的情况下将 32 位整数塞入其中。
  • 只是一个附录。如果使用 MSVC 2010,您应该能够编译/汇编它,并使用调试器逐步完成,并在 cvtsi2ss 和每个后续指令之后查看包括 XMM1 在内的寄存器。您可能已经发现 xmm1 中填充了什么浮点值。

标签: assembly x86 sse masm masm32


【解决方案1】:

您将 231-1 移动到 EAX 并将其从 32 位整数转换为单个(32 位)标量浮点数。

mov eax, 0x7FFFFFFF   // eax = 2^31 - 1
cvtsi2ss xmm1, eax    // convert eax to float --> xmm1

问题在于 IEEE754 32 位浮点数中没有足够的尾数来准确表示 231-1。它实际上被四舍五入到 2.147483648E9。有一个online binary converter 可以更好地描述这是如何发生的。整数 231-1 到单个标量浮点数 2.147483648E9 的转换是 demonstrated here


精确表示从 0 到 2 的每个整数31-1 占用 31 位。 32 位 IEEE 浮点数(带有23 + 1 implicit bit mantissa)可以exactly represent every integer with magnitude up to 224。在该范围之外,2 的幂是完全可表示的。

可以证明(用信息论)不可能设计一个 31 位编码,既能准确表示从 0 到 231-1 的所有整数,也能表示任何其他值。整数占用了所有的编码空间。如果这样的事情是可能的,你可以重复使用该技术将世界上所有的数据压缩成一个位。


0x80000000 结果是cvtss2sicvtsd2si 信号如何溢出。来自英特尔 insn 参考手册(有关链接,请参阅 wiki):

如果转换结果大于最大有符号双字整数,则浮点无效 引发异常,如果屏蔽此异常,则返回不定整数值(80000000H)。

这与整数回绕无关,也与浮点值比精确结果大一圈无关。

请注意,对于 64 位整数寄存器,cvtss2si rax, xmm1 可以产生高达0x7fffff8000000000 的结果,更大的浮点数产生0x8000000000000000“不定值”。这与英特尔手册中的文本描述相反,他们忘记更新 64 位操作数大小的最大值段落以匹配 cvtsd2si 所说的内容。您可以往返到单精度浮点数而不会产生溢出的最大整数是0x7fffffbfffffffff


如果您使用双标量,则尾数足以准确表示 231-1。整数 231-1 到双标量浮点数 2.147483647E9 的转换是 demonstrated here

正如 Jester 指出的那样,使用双(64 位)标量浮点数可以解决您的问题。该代码可能类似于:

double sample = 1.0f;

__asm
{
    mov eax, 0x7FFFFFFF   // eax = 2^31 - 1
    cvtsi2sd xmm1, eax    // convert eax to double float --> xmm1
    movsd xmm0, [sample]  // where 'sample' is of type double float
    mulsd xmm0, xmm1      // Get sample's quantum into xmm0
    cvtsd2si eax, xmm0    // Round quantum to the nearest integer --> eax
}

如果您想将 sample 保留为 32 位浮点数而不是我的示例中的双精度数,您可以将 movsd xmm0, [sample] 替换为 cvtss2sd xmm0, [sample]

鉴于此答案基于多个贡献者的输入,我已将其标记为社区 wiki,因此请随时编辑。

【讨论】:

  • 您好 Michael,感谢您提供的详尽、最有用的答案(标记为 答案)。如果我理解正确,我的原始代码将适合每个样本高达 25 位的编码深度(即幅度为 24 位),但对于 26-32,转换为单精度浮点数会产生(内在) 错误。
  • @Bliss :感谢您接受答案。由于这是一项集体努力,并且因为我将其标记为社区 wiki,因此没有人获得任何声誉(包括我自己),但现在至少会被标记为已关闭/已回答以供将来搜索的人使用。
  • 不应该将代码中最后的cvtsd2si 指令替换为cvtss2sd吗?
  • @Bliss 否,因为 xmm0 中的值旨在作为双精度值(它是使用 mulsd 等创建的)并且您希望结果为 int
  • 问题:使用我的原始代码,只是转换 1.0 有问题(导致溢出)或其他值(例如,幅度高于 2^24) ?我在问,因为如果它只有 1.0,那么建议的双精度代码将消耗双倍的 CPU 周期,只是“为了”单个最终值(1.0)。 第二个问题:在这种情况下,也许这种极端情况可以通过检查溢出等来特殊处理(保持单精度计算)?
猜你喜欢
  • 2022-01-01
  • 1970-01-01
  • 2023-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多