x86程序集(SSE):意外的乘法结果

时间:2016-02-16 22:52:44

标签: assembly x86 sse masm masm32

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

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

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

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

我在这里缺少什么?

{MSVC2010正用于测试。 } `

1 个答案:

答案 0 :(得分:3)

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

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

问题是IEEE754 32位浮点中没有足够的尾数来准确表示2 31 -1。它实际上被舍入到2.147483648E9。有一个online binary converter可以更好地描述这是如何发生的。将整数2 31 -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到2 31 -1的所有整数,并且能够代表任何其他价值观。整数耗尽所有编码空间。如果这样的事情是可能的,你可以反复使用这种技术将所有世界的数据压缩成一位。

0x80000000结果是cvtss2sicvtsd2si信号溢出的方式。从英特尔insn参考手册(参见 wiki获取链接):

  

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

它与整数环绕无关,或浮点值超出确切结果。

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

如果使用双标量,则有足够的尾数来准确表示2 31 -1。整数2 31 -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]

鉴于此答案是基于多个贡献者的输入,我将其标记为社区维基,因此可以自由编辑。