以下代码应将正(单精度)浮点数量化为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正用于测试。 } `
答案 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
结果是cvtss2si
和cvtsd2si
信号溢出的方式。从英特尔insn参考手册(参见x86 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]
鉴于此答案是基于多个贡献者的输入,我将其标记为社区维基,因此可以自由编辑。