CVTTSD2SI - 截断指令 - 使用具有“不精确”结果的舍入?

Bon*_*ero 4 x86 assembly sse floating-point-conversion

根据 Intel 文档CVTTSD2SI,如果转换不精确,则将 fp 值截断为 64 位有符号整数的指令将根据 MXCSR 舍入模式进行舍入。

但简单的截断转换怎么会不精确呢?我认为要么该值符合整数范围,要么不符合整数范围;如果不是,则不进行舍入,但值为 -1 << 63,并且可能会引发异常。

以下是英特尔文档的内容:

当转换不精确时,返回的值将根据 MXCSR 寄存器中的舍入控制位进行舍入。

Pet*_*des 7

当 FP 值还不是整数时,FP->整数转换是不精确的。例如,1.25 转换为整数1,舍入误差为 0.25。但完全1.0转换为. 这与with引发的 FP 异常标志相同,但不是由 引发的,因为 0.5 完全可以表示为 float 或 double。11.0 / 3.0divsd1.0 / 2.0

这句话是手册中的一个错误。当转换不精确时,CVTTSD2SI 始终使用截断,而不是 MXCSR 中当前的舍入模式(默认为最接近,平局为偶数)

CVTSD2SI 手册条目中存在完全相同的单句段落,该条目确实使用了 MXCSR,因此这是英特尔的复制/粘贴错误。


超出范围是另一种情况

如果不是,则不进行舍入,但值为 -1 << 63,并且可能会引发异常。

这是与不精确不同的一种例外。该cvttsd2si手册对此进行了介绍

如果转换结果超出有符号双字整数([具有 32 位目标])的范围限制,则会引发浮点无效异常,并且如果屏蔽此异常,则返回不定整数值 (80000000H)。

类似地,对于 64 位操作数大小,不确定值是 MSB 设置,其他位为零。无论 FP 值是正还是负,都使用相同的位模式。

(当然,它也可以是有效转换的结果,因为最负的 2 的补数是 2 的幂,因此可以精确地表示为双精度或偶数浮点数。更好的选择可能是奇数的某个大数值,就像80000001h,尽管这仍然可以是有效的 double->int32 转换的结果。但不是 float->int32 或(对于更宽的 64 位版本)double->int64。)

检查 Intel 手册以获取 MXCSR 中的全套标志。或者一些专门关于此的文章,例如https://softpixel.com/~cwright/programming/simd/sse.php

IE(无效操作异常)是与 FP 上溢 (OE) 或下溢 (UE) 不同的标志。


实验测试

为了确保这一点,我使用 GDB 进行了测试,display $mxcsr以在每一步之后显示(和解码)MXCSR。(在 i7-6700k Skylake 上,但此行为是必需的,因此其他 CPU 应该具有相同的行为。)

section .rodata
frac: dq 1.75
whole: dq 2.0

section .text
global _start
_start:
  stmxcsr [rsp-8]                   ; in the red zone; x86-64 SysV
  cvtsd2si  eax, [frac]
  ldmxcsr [rsp-8]
  cvttsd2si edx, [frac]

  ldmxcsr [rsp-8]
  cvttsd2si ecx, [whole]
 ;... fall off the end because I only want to single-step this, and can exit GDB before letting it step past here
Run Code Online (Sandbox Code Playgroud)
$ nasm -felf64 foo.asm && ld -o foo  foo.o 
$ gdb ./foo
(gdb) layout reg
 # and with recent GDB, layout n  to get regs + disassembly
(gdb) starti
(gdb) display $mxcsr
1: $mxcsr = [ IM DM ZM OM UM PM ]     
      # bunch of things masked, no raised exceptions
(gdb) si   a couple times
 # after <_start+5>     cvtsd2si eax,QWORD PTR [rip+0xff3]        # 0x402000
1: $mxcsr = [ PE IM DM ZM OM UM PM ]
         # PE = precision exception = inexact, plus the earlier mask flags
...
Run Code Online (Sandbox Code Playgroud)

每个指令ldmxcsr都会重新加载一个无异常引发的状态,这样我们就可以看到下一条指令做了什么。

  • cvtsd2si当将 1.75 转换为整数时,两者cvttsd2si都会设置 PE(不精确异常标志)。
  • 当将 2.0 转换为整数时,两者都清楚地表明了这一点。

我没有揭露任何异常,因为我只是希望将它们记录在 MXCSR 粘性位中,而不是捕获并获取 SIGFPE。