x86 程序集 (SSE):意外的乘法结果
x86 Assembly (SSE): Unexpected Multiplication Result
以下代码应将 positive(单精度)浮点数量化为 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
问题是:对于1.0f
的sample
值,最终结果(eax
值)是0x80000000 = 2^31
,超出范围。
预期结果将是 1.0 x (2^31 - 1) = (2^31 - 1) = 0x7FFFFFFF
.
而且这个值其实是-2^31
的2的补码表示(注意减号)
我在这里错过了什么?
{ 正在使用 MSVC2010 进行测试。 }
`
你将 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 that can better describe how this occurred. The conversion of integer 231-1 to a single scalar float 2.147483648E9 is demonstrated here
精确表示0到2的每一个整数31-1占31位。一个 32 位 IEEE 浮点数(带有 23 + 1 implicit bit mantissa) can exactly represent every integer with magnitude up to 224。在该范围之外,2 的幂可以精确表示。
可以证明(用信息论)不可能设计出一个31位的编码,既能准确表示0到231-1的所有整数,又能表示任何其他值。整数用完了所有编码 space。如果这样的事情是可能的,你可以重复使用该技术将世界上所有的数据压缩成一个比特。
0x80000000
结果是 cvtss2si
和 cvtsd2si
信号如何溢出。来自 Intel insn ref 手册(请参阅 x86 wiki 获取链接):
If a converted result is larger than the maximum signed doubleword integer, the floating-point invalid
exception is raised, and if this exception is masked, the indefinite integer value (80000000H) is returned.
这与整数回绕无关,或者浮点值是精确结果的一个。
请注意,对于 64 位整数寄存器,cvtss2si rax, xmm1
可以产生最大 0x7fffff8000000000
的结果,较大的浮点数产生 0x8000000000000000
"indefinite value"。这与英特尔手册中的文本描述相反,他们忘记更新 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]
鉴于此答案是基于多个贡献者的输入,我已将其标记为 community wiki,请随时编辑。
以下代码应将 positive(单精度)浮点数量化为 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
问题是:对于1.0f
的sample
值,最终结果(eax
值)是0x80000000 = 2^31
,超出范围。
预期结果将是 1.0 x (2^31 - 1) = (2^31 - 1) = 0x7FFFFFFF
.
而且这个值其实是-2^31
的2的补码表示(注意减号)
我在这里错过了什么?
{ 正在使用 MSVC2010 进行测试。 } `
你将 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 that can better describe how this occurred. The conversion of integer 231-1 to a single scalar float 2.147483648E9 is demonstrated here
精确表示0到2的每一个整数31-1占31位。一个 32 位 IEEE 浮点数(带有 23 + 1 implicit bit mantissa) can exactly represent every integer with magnitude up to 224。在该范围之外,2 的幂可以精确表示。
可以证明(用信息论)不可能设计出一个31位的编码,既能准确表示0到231-1的所有整数,又能表示任何其他值。整数用完了所有编码 space。如果这样的事情是可能的,你可以重复使用该技术将世界上所有的数据压缩成一个比特。
0x80000000
结果是 cvtss2si
和 cvtsd2si
信号如何溢出。来自 Intel insn ref 手册(请参阅 x86 wiki 获取链接):
If a converted result is larger than the maximum signed doubleword integer, the floating-point invalid exception is raised, and if this exception is masked, the indefinite integer value (80000000H) is returned.
这与整数回绕无关,或者浮点值是精确结果的一个。
请注意,对于 64 位整数寄存器,cvtss2si rax, xmm1
可以产生最大 0x7fffff8000000000
的结果,较大的浮点数产生 0x8000000000000000
"indefinite value"。这与英特尔手册中的文本描述相反,他们忘记更新 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]
鉴于此答案是基于多个贡献者的输入,我已将其标记为 community wiki,请随时编辑。