gpt4 book ai didi

assembly - 使用截断而不是使用x87 FPU舍入将浮点转换为整数而不是四舍五入

转载 作者:行者123 更新时间:2023-12-02 19:51:40 24 4
gpt4 key购买 nike

FISTP instruction更改为0.75到1(由于四舍五入)

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

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

最佳答案

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

  • 如果仍然使用 SSE2 指令,则可以使用SSE2指令将截断点的浮点值转换为整数值。 Peter Cordes's answer讨论了这种方法。 CVTTSD2SI 是标量版本,而 CVTTPD2DQ 是打包/矢量版本。

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

    如果您针对的是奔腾4或Athlon 64之前的x86-32处理器,那么SSE2指令将不可用。在这种情况下, SSE 指令可能仍然可用(奔腾3,Athlon XP和更高版本支持SSE)。 SSE仅支持单精度浮点运算,因此,如果不需要精度,则可以使用 CVTTSS2SI (标量)或 CVTTPS2DQ (打包/矢量)。不幸的是,您经常需要精度。请参阅下面的更好的解决方法。
  • 如果有 SSE3 指令可用(奔腾4 Prescott,某些Athlon 64s及更高版本),则可以使用 FISTTP 指令,类似于FISTP,但不管当前舍入模式如何,它总是会被截断。这是fuz's answer提出的解决方案。

    如果您已经在使用x87 FPU,但这是一个很好的解决方案,但是适用性有限,因为如果您要针对支持SSE3的芯片,则它们必然支持SSE2,因此您应该使用SSE指令来执行所有浮点运算操纵。唯一的异常(exception)是,如果您真的需要x87 FPU提供的扩展的80位精度进行中间计算(SSE2限于64位 double )。
  • 如果您被困在传统的x86-32处理器上,并且在不使用SSE的情况下使用 x87 FPU ,则仍然没有选择余地。有几种快速位旋转方法。这些不是我最初的创新-代码散布在Internet的各个位置,我只是对其进行了整理和微调,所以我不能完全相信,也不能引用特定的来源。 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语言编写的一个简单的“truncate”函数所能做的,除了它可以无分支地执行它之外:
    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,尤其是因为我重新使用了“adjustment”值来实现其他类型的快速舍入。

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

    我意识到所有这些看起来都很复杂,但这确实比调整舍入模式,进行舍入和重新设置舍入模式要快得多。我已经在各种处理器和各种代码路径上对它进行了广泛的基准测试,但从未发现它会变慢。但是我在C代码中使用它,在C代码中,标准要求编译器发出恢复四舍五入模式的代码。 如果您是手工编写程序集,并且需要截断,只需将FPU的舍入模式切换为“截断”一次,然后将其保留即可。

    此纠错码也有一个 double 版本。关键是要意识到符号位位于64位double的高32位中,因此您仍然只需要一个32位寄存器。

    但是, double 版本并非没有错误!极其接近整数的浮点值将四舍五入为最接近的整数,而不是被截断(例如,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
  • 关于assembly - 使用截断而不是使用x87 FPU舍入将浮点转换为整数而不是四舍五入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41112110/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com