【问题标题】:Convert a floating point to an integer with truncation instead of rounding using the x87 FPU使用 x87 FPU 将浮点数转换为带有截断而不是舍入的整数
【发布时间】:2017-04-27 23:36:09
【问题描述】:

FISTP instruction 将 0.75 更改为 1(因为四舍五入)

我希望 0.75 变成 0,而不是 1。

是否有 FIST/FISTP 的替代方法可以截断而不是舍入?

【问题讨论】:

  • 如果你阅读你链接的页面,你可以看到它说:根据FPU控制字的RC字段指定的舍入模式。所以显然你需要改变它。您可能还对Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 1: Basic Architecture中的4.8.4 Rounding部分感兴趣。
  • 好吧,回去再读一遍。 FISTP 没有问题,但需要更改舍入模式。是的,截断 模式之一。另请阅读我指给您的手册部分。
  • 或者,如果你可以使用 SSE,有一个 CVTTSS2SI--Convert with Truncation 指令。
  • 如果您有 Pentium IV 或更新版本,请尝试 fisttp
  • @fuz haha​​ 那也存在。错过了。好旧的 FPU。

标签: assembly nasm x87


【解决方案1】:

SSE3 instruction set 还引入了fisttp 指令。它的工作原理类似于fistp 指令,它可以将浮点数存储为 32 位整数(在进程中弹出堆栈),除了它总是截断该值,而不管当前舍入模式。

这是一个如何使用它的示例:

FLD    QWORD PTR [esi] ; load 64 bit floating point number
FISTTP DWORD PTR [edi] ; truncate and store as 32 bit integer

或在 AT&T 语法中:

fldl    (%esi)
fisttpl (%edi)

如果您没有支持 SSE3 的处理器,在确保舍入模式设置为“截断”后,您可以使用 fistp 指令获得类似的结果。

sub    esp,0x4               ; make space for the control word
fstcw  WORD PTR [esp]        ; store the FPU control word
fstcw  WORD PTR [esp+0x2]    ; store another copy
or     WORD PTR [esp],0x0c00 ; set rounding mode to "truncate"
fldcw  WORD PTR [esp]        ; load updated control word
fld    QWORD PTR [esi]       ; load floating point number
fistp  WORD PTR [edi]        ; truncate to integer
fldcw  WORD PTR [esp+0x2]    ; restore control word

或在 AT&T 语法中:

sub $4,%esp
fstcw (%esp)
fstcw 2(%esp)
orw $0x0c00,(%esp)
fldcw (%esp)
fldl (%esi) 
fistp (%edi)
fldcw 2(%esp)

如果您的代码不能在 80286 或更早版本上运行,您可能希望使用 fnstcw 而不是 fstcw 来为每条指令节省一个字节,但代价是代码可能无法在真正的 8087 上运行。

【讨论】:

  • FISTTP 是 SSE3 的一部分。所以它是 P4 的新功能,只是指出它没有与 SSE3 分开的特殊功能位,而且比 P4 更新的 AMD CPU 缺少它。
  • @PeterCordes 感谢您提供的信息!
  • Peter 是对的,FISTTP 是 SSE3 的一部分,但这意味着它在 all Pentium 4s 上可用。 SSE3 是专门与 Prescott 一起引入的,它大约是 P4 生命周期的一半。我已经相应地更新了你的答案,因为彼得和我的迂腐不应该破坏你的工作流程。 :-)
  • @CodyGray:哦,对了,第一代 P4 引入了 SSE2。很好的收获。
【解决方案2】:

如果您不必首先使用大部分已过时的 x87,那么所有 SSE/SSE2 转换指令(标量和打包)都有一个截断版本,可让您(和 C 编译器)有效地实现 C 语义,而无需更改舍入模式。

例如CVTSD2SICVTTSD2SI,
CVTPS2DQCVTTPS2DQ,都只需要 SSE2。

CVTTSS2SI 只需要 SSE。

(SSE2 在 XMM 寄存器中增加了对双精度浮点数和整数向量的支持,因此 SSE1 只有标量单浮点数到整数寄存器,没有压缩浮点数到压缩 DWORD 向量。)

是的,我知道 OP 说他们不能使用 SSE,但这是这个问题标题的正确答案,希望对未来的搜索者有用。

【讨论】:

    【解决方案3】:

    您在这里确实有很多选择:

    1. 如果您仍然使用 SSE2 指令,那么您可以使用 SSE2 指令将浮点值转换为带有截断的整数值。 Peter Cordes's answer 讨论了这种方法。 CVTTSD2SI 是标量版本,CVTTPD2DQ 是打包/向量版本。

      如果您的目标是 x86-64,SSE2 将始终可用,这是您应该用于所有浮点运算的。 x87 FPU 在 x86-64 上已完全过时。

      如果您的目标是奔腾 4 或 Athlon 64 之前的 x86-32 处理器,则 SSE2 指令将不可用。在这种情况下,SSE 指令可能仍然可用(SSE 受 Pentium 3、Athlon XP 和更高版本支持)。 SSE 仅支持 单精度 浮点运算,因此如果您不需要精度,可以使用 CVTTSS2SI(标量)或 @987654325 @(打包/矢量)。不幸的是,您经常需要精度;请参阅下文以获得更好的解决方法。

    2. 如果 SSE3 指令可用(Pentium 4 Prescott、某些 Athlon 64 和更高版本),那么您可以使用 FISTTP 指令,类似于 @987654335 @,除了它总是截断,不管当前的舍入模式。这就是fuz's answer 提出的解决方案。

      如果您已经在使用 x87 FPU,这是一个非常好的解决方案,但适用性有限,因为如果您的目标是支持 SSE3 的芯片,它们必然支持 SSE2,因此您应该使用 SSE 指令来完成所有操作浮点操作。唯一的例外是如果您真的需要 x87 FPU 提供的扩展 80 位精度来进行中间计算(SSE2 仅限于 64 位双精度)。

    3. 如果您停留在旧版 x86-32 处理器上并使用没有 SSE 的 x87 FPU,您仍然没有选择余地。有几种快速的位旋转方法。这些不是我的原创——代码散布在互联网上的各个地方,我只是对它们进行了一些整理和微调,所以我不能完全相信,也不能引用特定的来源。 Here is one such source.

      对于单精度浮点值,整个位表示适合 32 位寄存器,因此实现很简单(假设要截断的浮点值位于 x87 FPU 堆栈的顶部):

      ; Retrieve the bit representation of the original floating-point value.
      push  eax
      fst   DWORD PTR [esp]
      mov   eax, DWORD PTR [esp]
      
      ; Twiddle those raw bits.
      and   eax, 080000000H
      xor   eax, 0BEFFFFFFH
      
      ; Store those manipulated bits back in memory, since we can't load        
      ; directly from a register to the x87 FPU stack.
      mov   DWORD PTR [esp], eax
      
      ; Add the modified value to the original value at the top of the stack.
      fadd  DWORD PTR [esp]
      
      ; Round the adjusted floating-point value to an integer.
      ; (Our bit manipulation ensures that this will always truncate,
      ; regardless of the current rounding mode.)
      fistp DWORD PTR [esp]
      
      ; ... do something with the result in ESP
      
      pop   eax
      

      另一种实现使用“调整”值的静态数组,我们根据原始浮点值的“符号性”对其进行索引。这基本上是一个用 C 语言编写的幼稚的“截断”函数会做的事情,除了它是无分支的:

      const uint32_t kSingleAdjustments[2] = { 0xBEFFFFFF,  /* -0.49999997f */
                                               0x3EFFFFFF   /* +0.49999997f */ };
      
      ; Retrieve the bit representation of the floating-point value.
      push  eax
      fst   DWORD PTR [esp]
      mov   eax, DWORD PTR [esp]
      
      ; Isolate the sign bit.
      shr   eax, 31
      
      ; Use the sign bit as an index into the array of values to add the appropriate
      ; adjustment value to the original floating-point value at the top of the stack.
      ; (NOTE: This syntax is for MSVC's inline asm; translate as necessary.)
      fadd  DWORD PTR [kSingleAdjustments + (eax * TYPE kSingleAdjustments)]
      
      ; Round the adjusted floating-point value to an integer.
      ; (Our adjustment ensures that it will be truncated, regardless of rounding mode.)
      fistp DWORD PTR [esp]
      
      ; ... do something with the result in ESP
      
      pop   eax
      

      我的基准测试表明,第二个变体在 Intel 处理器上速度更快,但在 AMD 上速度较慢(特别是 Athlon XP 和 Athlon 64)。我最终决定为我的库选择方法 #2,特别是因为我重新使用“调整”值来实现其他类型的快速舍入。

      请注意,最后的 FISTP 指令同时支持 m32m64 操作数,因此如果您想截断为 64 位整数以获得更高的精度,这是可能的。只要记住在堆栈上分配两倍的空间,然后使用fistp QWORD PTR, [esp] 而不是fistp DWORD PTR, [esp]

      我意识到这一切看起来都非常复杂,但是 这确实比调整舍入模式、进行舍入和设置舍入模式要快得多。我已经在各种处理器和各种代码路径中对它进行了广泛的基准测试,从未发现它变慢了。但我在 C 代码中使用它,标准要求编译器发出恢复舍入模式的代码。 如果您是手动编写汇编,并且需要截断,只需将 FPU 的舍入模式切换为“截断”一次并保留它。


      这个位旋转代码也有一个双精度版本。关键是要意识到符号位位于 64 位双精度的高 32 位,因此您仍然只需要一个 32 位寄存器。

      但是,双精度版本并非没有错误!非常接近整数的浮点值将向上舍入到最接近的整数,而不是被截断(eg,4.99999977 被错误地四舍五入为 5,而不是被截断为 4 )。比我聪明且有更多时间解决此问题的人可能会想出解决此问题的方法,但我对大多数情况下的准确性感到满意,尤其是考虑到速度的大幅提升。

      const uint64_t kDoubleAdjustments[2] = { 0xBFDFFFFF00000000,
                                               0x3FDFFFFF00000000 };
      
      sub   esp, 8
      fst   QWORD PTR [esp]
      mov   eax, DWORD PTR [esp+4]   ; we only need the upper 32 bits
      
      shr   eax, 31
      fadd  QWORD PTR [kDoubleAdjustments + (eax * TYPE kDoubleAdjustments)]
      
      fistp DWORD PTR [esp]
      
      ; ... do something with the result in ESP
      
      add   esp, 8
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-09
      • 2011-12-27
      • 1970-01-01
      • 2015-04-10
      • 2011-01-26
      • 1970-01-01
      相关资源
      最近更新 更多